1
0
mirror of https://github.com/LDDestroier/CC/ synced 2024-12-04 23:39:58 +00:00
ldd-CC/pain.lua
LDDestroier 3d802fc3e8
Replaced all sleep(0) with sleep(0.05)
CraftOS-PC handles sleep(0) as literally sleeping for no time, as opposed to how CC:Tweaked handles it by waiting 0.05 seconds (one tick).
2020-11-23 06:12:32 -05:00

3571 lines
99 KiB
Lua
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

--[[
PAIN image editor for ComputerCraft
Get it with
wget https://raw.githubusercontent.com/LDDestroier/CC/master/pain.lua pain
pastebin get wJQ7jav0 pain
std ld pain pain
--]]
local plc = {} -- pain local, to avoid upvalue limit
plc.askToSerialize = false
plc.defaultSaveFormat = 4 -- will change if importing image, or making new file with extension in name
--[[
plc.defaultSaveFormat possible parameters:
1. NFP (paint)
2. NFT (npaintpro)
3. BLT (blittle)
4. Native PAIN
5. GIF
6. UCG
--]]
plc.progname = fs.getName(shell.getRunningProgram())
plc.apipath = ".painapi"
local painconfig = {
undoBufferSize = 8, -- amount of times undo will save your neck
readNonImageAsNFP = true, -- reads non-image files as NFP images
useFlattenGIF = true, -- will flatten compressed GIFs
gridBleedThrough = false, -- will draw grid instead of character value of dots
doFillDiagonal = false, -- checks for diagonal dots when using fill tool
doFillAnimation = false, -- whether or not to animate the fill tool
useSetVisible = false, -- whether or not to use term.current().setVisible, if possible
}
local useConfig = function(mode)
if mode == "save" then
local file = fs.open(fs.combine(plc.apipath,"painconfig"), "w")
file.write(textutils.serialize(painconfig))
file.close()
elseif mode == "load" then
if fs.exists(fs.combine(plc.apipath,"painconfig")) then
local file = fs.open(fs.combine(plc.apipath,"painconfig"), "r")
painconfig = textutils.unserialize(file.readAll())
file.close()
end
end
end
useConfig("load")
useConfig("save")
plc.displayHelp = function()
print(plc.progname)
print(plc.progname.." <filename>")
print(plc.progname.." [-h/--help]")
print("Press F1 in program for more.")
end
plc.tsv = function(visible)
if term.current().setVisible and painconfig.useSetVisible then
term.current().setVisible(visible)
end
end
--local undoBuffer
plc.undoPos = 1
plc.pMode = 0
local scr_x, scr_y = term.getSize()
local screenEdges = {
scr_x,
scr_y,
}
plc.tArg = {...}
if (plc.tArg[1] == "--help" or plc.tArg[1] == "-h") and shell then
return plc.displayHelp()
end
if plc.tArg[2] == "view" then
plc.pMode = 1
elseif (plc.tArg[2] == "moo") and (not fs.exists("moo")) then
return print("This PAIN does not have Super Cow Powers.")
end
-- plc.fileName
if (not term.isColor()) and (plc.pMode ~= 1) then
error("PAIN only works with Advanced Computers.")
end
local barmsg = "Press F1 for help."
local paintEncoded
local lastPaintEncoded
local frame = 1
local doRender = false
local metaHistory = {}
local firstTerm, blittleTerm = term.current()
plc.bepimode = false -- this is a family-friendly program! now stand still while I murder you
plc.evenDrawGrid = true -- will you evenDraw(the)Grid ?
plc.renderBlittle = false -- whether or not to render all in blittle
plc.firstBG = term.getBackgroundColor()
plc.firstTX = term.getTextColor()
plc.changedImage = false
plc.isCurrentlyFilling = false
local theClipboard = {}
local _
local tableconcat = table.concat
local rendback = {
b = colors.black,
t = colors.gray,
}
local grid
local yield = function()
os.queueEvent("yield")
os.pullEvent("yield")
end
local paint = {
scrollX = 0,
scrollY = 0,
t = colors.gray,
b = colors.white,
m = 1, -- in case you want to use PAIN as a level editor or something
c = " ",
doGray = false,
}
local boxchar = {topLeft = true, topRight = true, left = true, right = true, bottomLeft = true, bottomRight = true}
local swapColors = false -- swaps background and text colors, for use with those tricky box characters
local scrollX, scrollY = 0, 0
local keysDown = {}
local miceDown = {}
local doRenderBar = 1 -- Not true or false
local fixstr = function(str)
return str:gsub("\\(%d%d%d)",string.char)
end
local choice = function(input,breakkeys,returnNumber)
local fpos = 0
repeat
event, key = os.pullEvent("key")
if type(key) == "number" then key = keys.getName(key) end
if key == nil then key = " " end
if type(breakkeys) == "table" then
for a = 1, #breakkeys do
if key == breakkeys[a] then
return ""
end
end
end
fpos = string.find(input, key)
until fpos
return returnNumber and fpos or key
end
local explode = function(div,str)
if (div=='') then return false end
local pos,arr = 0,{}
for st,sp in function() return string.find(str,div,pos,true) end do
arr[#arr+1] = str:sub(pos,st-1)
pos = sp + 1
end
arr[#arr+1] = str:sub(pos)
return arr
end
local cutString = function(max_line_length, str) -- from stack overflow
local lines = {}
local line
str:gsub('(%s*)(%S+)',
function(spc, word)
if not line or #line + #spc + #word > max_line_length then
lines[#lines+1] = line
line = word
else
line = line..spc..word
end
end
)
lines[#lines+1] = line
return lines
end
local getDrawingCharacter = function(topLeft, topRight, left, right, bottomLeft, bottomRight) -- thank you oli414
local data = 128
if not bottomRight then
data = data + (topLeft and 1 or 0)
data = data + (topRight and 2 or 0)
data = data + (left and 4 or 0)
data = data + (right and 8 or 0)
data = data + (bottomLeft and 16 or 0)
else
data = data + (topLeft and 0 or 1)
data = data + (topRight and 0 or 2)
data = data + (left and 0 or 4)
data = data + (right and 0 or 8)
data = data + (bottomLeft and 0 or 16)
end
return {char = string.char(data), inverted = bottomRight}
end
local cutUp = function(len,tbl)
local output = {}
local e = 0
local s
for a = 1, #tbl do
if #(tbl[a]:gsub(" ","")) == 0 then
s = {""}
else
s = cutString(len,tbl[a])
end
for b = 1, #s do
output[#output+1] = s[b]
end
end
return output
end
local getEvents = function(...)
local arg, output = table.pack(...)
while true do
output = {os.pullEvent()}
for a = 1, #arg do
if type(arg[a]) == "boolean" then
if doRender == arg[a] then
return {}
end
elseif output[1] == arg[a] then
return table.unpack(output)
end
end
end
end
local sanitize = function(sani,tize)
local _,x = string.find(sani,tize)
if x then
return sani:sub(x+1)
else
return sani
end
end
local ro = function(input, max)
return math.floor(input % max)
end
local guiHelp = function(inputText)
term.redirect(firstTerm)
scr_x, scr_y = term.current().getSize()
local _helpText = inputText or [[
'PAIN' super-verbose help page
Programmed by LDDestroier
(use UP/DOWN or scrollwheel, exit with Q)
If you wish to use PAIN to its fullest, read everything here.
You'll be image-editing like a pro in no time flat.
Syntax:
>pain <filename> [view] [x] [y]
>pain [-n]
>pain [-h/--help]
[view]: renders the image once (optionally scrolling with [x] and [y])
"-n" or no arguments: Create new document, declare name upon saving
"-h" or "--help": Display short syntax help
You can see what colors are selected based on the word "PAIN" on the hotbar.
Hotkeys:
left/right ctrl: Toggle the menu
left click:
+left shift = Drag and let go to draw a line
-alone = Place a dot
Right Click: delete pixel
Middle Click, or "T": Place text down with current colors; cancel with X
"Z":
+LeftAlt = Redo
-alone = Undo
"P": Pick colors from position onscreen; cancel with X
"N":
+LeftShift = Change character to that of a special character
-alone = Change box character for drawing
(cancel with CTRL, N, or by clicking outside)
"[" or mouse scroll down:
+LeftShift = Change to previous text color
-alone = Change to previous background color
"]" or mouse scroll up:
+LeftShift = Change to next text color
-alone = Change to next background color
"F1":
-alone = Access help screen
"F3:"
-alone = View all connected monitors
Spacebar:
+LeftShift = Toggle background grid
-alone = Toggle bar visibility
Arrow keys:
+LeftShift = Displaces the entire frame
+Tab = Moves canvas one pixel at a time
-alone = Looks around the canvas smoothly
"+" (or equals):
+LeftAlt = Swap the current frame with the next frame
+LeftShift = Merge the current frame atop the next frame
+RightShift = If you are making a new frame, duplicates the last frame
-alone = Change to next frame
"-":
+LeftAlt = Swap the current frame with the previous frame
+LeftShift = Merge the current frame atop the previous frame
-alone = Change to previous frame
(oh good, you're actually reading this stuff)
"A": Set the coordinates to 0,0
"B": Toggle redirect to blittle, to preview in teletext characters
"c":
+LeftAlt = Select region to copy to specified clipboard
-alone = Input coordinates to scroll over to
"LeftAlt + X": Select region to cut to specified clipboard
"LeftAlt + X": Pastes from specified clipboard
"G": toggle grayscale mode.
Everything is in shades of gray.
If you Save, it saves in grayscale.
"F":
+LeftShift = fill all empty pixels with background color and selected box character
-alone = activate fill tool - click anywhere to fill with color
"M": set metadata for pixels (for game makers, otherwise please ignore)
==================================
Thy Menu (accessible with CTRL):
==================================
Left click on a menu item to select it.
If you click on the menubar, release on an option to select it.
"File > Save"
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't use text or multiple frame.
"File > Save As"
Same as "File > Save", but you change the filename.
"File > Export"
Exports current frame to NFP, NFT, BLT, or the horribly inefficient PAIN format.
"File > Open"
Opens up a file picker for you to change the image currently being edited.
"Edit > Delete Frame"
Deletes the current frame. Tells you off if you try to delete the only frame.
"Edit > Clear"
Deletes all pixels on the current frame.
"Edit > Crop Frame"
Deletes all pixels that are outside of the screen.
"Edit > Change Box Character"
Opens the block character selection. Used for making those delicious subpixel pictures.
"Edit > Change Special Character"
Opens the special character selector, which lets you change the paint character to that of byte 0 to 255.
"Edit > BLittle Shrink"
Shrinks the current frame using the BLittle API. Very lossy unless you use one color, or are careful with how you use colors. You can set "Always Render Grid" to true to assist in making blocky graphics.
"Edit > BLittle Grow"
Grows the image by (2x, 3y) to reverse the effects of "BLittle Shrink". This isn't lossy, since all it does is inflate an image's size and converts the corresponding block characters.
"Edit > Copy"
Drag to select a region of the screen, and save it in a clipboard of a specified name.
"Edit > Cut"
Same as Copy, but will delete the selected region on the screen.
"Edit > Paste"
Takes the contents of the specified clipboard, and plops it on the canvas where the mouse is.
(The mouse will indicate the top-left corner of the pasted selection)
"Set > ..."
Each option will toggle a config option (or set it's value to something else).
Changing a value is saved automatically, and effective immediately.
"Window > Set Screen Size"
Sets the sizes of the screen border references displayed on the canvas.
You can also input the name of a monitor object, and it will use its size instead.
"Window > Set Grid Colors"
Sets the backdrop colors to your currently selected color configuration.
"About > PAIN"
Tells you about PAIN and its developer.
"About > File Formats"
Tells you the ins and outs of the file formats, and a brief description of their creators.
"About > Help"
Opens up this help page.
"Exit"
Closes PAIN. I know, riviting stuff. You can close out of this help page with "Q", speaking of.
I hope my PAIN causes you joy.
]]
_helpText = explode("\n",_helpText)
helpText = cutUp(scr_x,_helpText)
local helpscroll = 0
term.setBackgroundColor(colors.gray)
term.setTextColor(colors.white)
term.clear()
local evt, key
while true do
term.clear()
for a = 1, scr_y do
term.setCursorPos(1,a)
term.clearLine()
write(helpText[a-helpscroll] or "")
end
repeat
evt,key = os.pullEvent()
until evt == "key" or evt == "mouse_scroll"
if evt == "key" then
if key == keys.up then
helpscroll = helpscroll + 1
elseif key == keys.down then
helpscroll = helpscroll - 1
elseif key == keys.pageUp then
helpscroll = helpscroll + scr_y
elseif key == keys.pageDown then
helpscroll = helpscroll - scr_y
elseif (key == keys.q) or (key == keys.space) then
doRender = true
if plc.renderBlittle then term.redirect(blittleTerm) end
scr_x, scr_y = term.current().getSize()
return
end
elseif evt == "mouse_scroll" then
helpscroll = helpscroll - key
end
if helpscroll > 0 then
helpscroll = 0
elseif helpscroll < -(#helpText-(scr_y-3)) then
helpscroll = -(#helpText-(scr_y-3))
end
end
end
local tableRemfind = function(tbl, str)
local out = tbl
for a = 1, #tbl do
if tbl[a] == str then
table.remove(out,a)
return out,a
end
end
return {}
end
local stringShift = function(str,amt)
return str:sub(ro(amt-1,#str)+1)..str:sub(1,ro(amt-1,#str))
end
local deepCopy
deepCopy = function(obj)
if type(obj) ~= 'table' then return obj end
local res = {}
for k, v in pairs(obj) do res[deepCopy(k)] = deepCopy(v) end
return res
end
local clearLines = function(y1, y2)
local cx,cy = term.getCursorPos()
for y = y1, y2 do
term.setCursorPos(1,y)
term.clearLine()
end
term.setCursorPos(cx,cy)
end
local renderBottomBar = function(txt,extraClearY)
term.setCursorPos(1,scr_y - math.floor(#txt/scr_x))
term.setBackgroundColor(colors.lightGray)
term.setTextColor(colors.black)
clearLines(scr_y - (math.floor(#txt/scr_x) - (extraClearY or 0)), scr_y)
return write(txt)
end
local bottomPrompt = function(txt,history,cho,breakkeys,returnNumber,writeIndent)
local writeIndent = renderBottomBar(txt,writeIndent)
local out
sleep(0.05)
if cho then
out = choice(cho,breakkeys,returnNumber)
else
out = read(_,history)
end
return out, writeIndent
end
local makeSubMenu = function(x,y,options)
local longestLen = 0
for a = 1, #options do
if #options[a] > longestLen then
longestLen = #options[a]
end
end
longestLen = longestLen + 1
term.setTextColor(colors.black)
local sel = 1
local rend = function()
for a = #options, 1, -1 do
term.setCursorPos(x or 1, ((y or (scr_y-1)) - (#options-1)) + (a - 1))
term.setBackgroundColor(a == sel and colors.white or colors.lightGray)
term.write(options[a])
term.setBackgroundColor(colors.lightGray)
term.write((" "):rep(longestLen-#options[a]))
end
end
local usingMouse = false
while true do
rend()
local evt, key, mx, my = os.pullEvent()
if evt == "key" then
if key == keys.up then
sel = sel - 1
elseif key == keys.down then
sel = sel + 1
elseif (key == keys.enter) or (key == keys.right) then
return sel, longestLen
elseif (key == keys.leftCtrl) or (key == keys.rightCtrl) or (key == keys.backspace) or (key == keys.left) then
return false, longestLen
end
elseif evt == "mouse_drag" or evt == "mouse_click" then
if (mx >= x) and (mx < x+longestLen) and (my <= y and my > y-#options) then
sel = math.min(#options,math.max(1,(my+#options) - y))
usingMouse = true
else
usingMouse = false
if evt == "mouse_click" then
return false, longestLen
end
end
elseif evt == "mouse_up" then
if usingMouse then
return sel, longestLen
end
end
if sel > #options then sel = 1 elseif sel < 1 then sel = #options end
end
end
local getDotsInLine = function( startX, startY, endX, endY ) -- stolen from the paintutils API...nwehehehe
local out = {}
startX = math.floor(startX)
startY = math.floor(startY)
endX = math.floor(endX)
endY = math.floor(endY)
if startX == endX and startY == endY then
out = {{x=startX,y=startY}}
return out
end
local minX = math.min( startX, endX )
if minX == startX then
minY = startY
maxX = endX
maxY = endY
else
minY = endY
maxX = startX
maxY = startY
end
local xDiff = maxX - minX
local yDiff = maxY - minY
if xDiff > math.abs(yDiff) then
local y = minY
local dy = yDiff / xDiff
for x=minX,maxX do
out[#out+1] = {x=x,y=math.floor(y+0.5)}
y = y + dy
end
else
local x = minX
local dx = xDiff / yDiff
if maxY >= minY then
for y=minY,maxY do
out[#out+1] = {x=math.floor(x+0.5),y=y}
x = x + dx
end
else
for y=minY,maxY,-1 do
out[#out+1] = {x=math.floor(x+0.5),y=y}
x = x - dx
end
end
end
return out
end
local movePaintEncoded = function(pe,xdiff,ydiff)
local outpootis = deepCopy(pe)
for a = 1, #outpootis do
outpootis[a].x = outpootis[a].x+xdiff
outpootis[a].y = outpootis[a].y+ydiff
end
return outpootis
end
local clearRedundant = function(dots)
local input = {}
local pheight = 0
local pwidth = 0
local minX, minY = 0, 0
for a = 1, #dots do
pheight = math.max(pheight, dots[a].y)
pwidth = math.max(pwidth, dots[a].x)
minX = math.min(minX, dots[a].x)
minY = math.min(minY, dots[a].y)
end
for a = 1, #dots do
if not input[dots[a].y] then input[dots[a].y] = {} end
input[dots[a].y][dots[a].x] = dots[a]
end
local output = {}
local frame = 0
for y = minY, pheight do
for x = minX, pwidth do
if input[y] then
if input[y][x] then
output[#output+1] = input[y][x]
end
end
if frame >= 50 then
-- yield()
frame = 0
end
end
end
return output
end
local grayOut = function(color)
local c = deepCopy(_G.colors)
local grays = {
[c.white] = c.white,
[c.orange] = c.lightGray,
[c.magenta] = c.lightGray,
[c.lightBlue] = c.lightGray,
[c.yellow] = c.white,
[c.lime] = c.lightGray,
[c.pink] = c.lightGray,
[c.gray] = c.gray,
[c.lightGray] = c.lightGray,
[c.cyan] = c.lightGray,
[c.purple] = c.gray,
[c.blue] = c.gray,
[c.brown] = c.gray,
[c.green] = c.lightGray,
[c.red] = c.gray,
[c.black] = c.black,
}
if (not color) or (color == " ") then return color end
local newColor = grays[color] or 1
return newColor
end
local getOnscreenCoords = function(tbl,_x,_y)
local screenTbl = {}
for a = 1, #tbl do
if tbl[a].x+paint.scrollX > 0 and tbl[a].x+paint.scrollX <= scr_x then
if tbl[a].y+paint.scrollY > 0 and tbl[a].y+paint.scrollY <= scr_y then
screenTbl[#screenTbl+1] = {tbl[a].x+paint.scrollX,tbl[a].y+paint.scrollY}
end
end
end
if not _x and _y then
return screenTbl
else
for a = 1, #screenTbl do
if screenTbl[a][1] == _x and screenTbl[a][2] == _y then
return true
end
end
return false
end
end
local clearAllRedundant = function(info)
local output = {}
for a = 1, #info do
output[a] = clearRedundant(info[a])
if a % 4 == 0 then yield() end
end
return output
end
local saveFile = function(path,info)
local output = clearAllRedundant(info)
local fileout = textutils.serialize(output):gsub(" ",""):gsub("\n",""):gsub(" = ","="):gsub(",}","}"):gsub("}},{{","}},\n{{")
if #fileout >= fs.getFreeSpace(fs.getDir(path)) then
barmsg = "Not enough space."
return
end
local file = fs.open(path,"w")
file.write(fileout)
file.close()
end
local renderBar = function(msg,dontSetVisible)
if (doRenderBar == 0) or plc.renderBlittle then return end
if not dontSetVisible then plc.tsv(false) end
term.setCursorPos(1,scr_y)
term.setBackgroundColor(colors.lightGray)
term.setTextColor(colors.black)
term.clearLine()
term.setBackgroundColor(paint.b or rendback.b)
term.setTextColor(paint.t or rendback.t)
term.setCursorPos(2,scr_y)
term.write("PAIN")
term.setBackgroundColor(colors.lightGray)
term.setTextColor(colors.black)
local fmsg = tableconcat({"Fr:",frame,"/",#paintEncoded," (",paint.scrollX,",",paint.scrollY,")"})
term.setCursorPos(7,scr_y)
term.write(msg)
term.setCursorPos(scr_x-(#fmsg),scr_y)
term.write(fmsg)
if not dontSetVisible then plc.tsv(true) end
end
local tableFormatPE = function(input)
local doot = {}
local pwidths = {}
local pheight = 0
for k, dot in pairs(input) do
pwidths[dot.y] = math.max((pwidths[dot.y] or 0), dot.x)
pheight = math.max(pheight, dot.y)
doot[dot.y] = doot[dot.y] or {}
doot[dot.y][dot.x] = {
char = dot.c,
text = CTB(dot.t),
back = CTB(dot.b)
}
end
for y = 1, pheight do
pwidths[y] = pwidths[y] or 0
if doot[y] then
for x = 1, pwidths[y] do
doot[y][x] = doot[y][x] or {
text = " ",
back = " ",
char = " ",
}
end
else
doot[y] = false
end
end
return doot, pheight, pwidths
end
CTB = function(_color) --Color To Blit
local blitcolors = {
[0] = " ",
[colors.white] = "0",
[colors.orange] = "1",
[colors.magenta] = "2",
[colors.lightBlue] = "3",
[colors.yellow] = "4",
[colors.lime] = "5",
[colors.pink] = "6",
[colors.gray] = "7",
[colors.lightGray] = "8",
[colors.cyan] = "9",
[colors.purple] = "a",
[colors.blue] = "b",
[colors.brown] = "c",
[colors.green] = "d",
[colors.red] = "e",
[colors.black] = "f",
}
if _color == nil then return nil end
return blitcolors[_color] or "f"
end
BTC = function(_color,allowZero) --Blit To Color
local blitcolors = {
[" "] = allowZero and 0 or nil,
["0"] = colors.white,
["1"] = colors.orange,
["2"] = colors.magenta,
["3"] = colors.lightBlue,
["4"] = colors.yellow,
["5"] = colors.lime,
["6"] = colors.pink,
["7"] = colors.gray,
["8"] = colors.lightGray,
["9"] = colors.cyan,
["a"] = colors.purple,
["b"] = colors.blue,
["c"] = colors.brown,
["d"] = colors.green,
["e"] = colors.red,
["f"] = colors.black,
}
if _color == nil then return nil end
return blitcolors[_color]
end
local renderPainyThings = function(xscroll,yscroll,doGrid)
local yadjust = (plc.renderBlittle and 0 or doRenderBar)
if plc.bepimode then
grid = {
"Bepis",
"episB",
"pisBe",
"isBep",
"sBepi",
}
else
grid = {
"%%..",
"%%..",
"%%..",
"..%%",
"..%%",
"..%%",
}
end
term.setBackgroundColor(rendback.b)
term.setTextColor(rendback.t)
local badchar = "/"
local blittlelabel = "blittle max"
local screenlabel = "screen max"
local dotBuffChar, dotBuffBack = "", "" --only used if gridBleedThrough is true
local doot
if doGrid then
for y = 1, scr_y - yadjust do
term.setCursorPos(1,y)
-- the single most convoluted line I've ever written that works, and I love it
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))
term.setCursorPos((xscroll <= 0) and (1-xscroll) or 0,y)
if ((screenEdges[2]+1)-yscroll) == y then --regular limit
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]) )
elseif (((screenEdges[2]*3)+1)-yscroll) == y then --blittle limit
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) )
end
-- Stupid easter eggs, ho! --
if 1000-yscroll == y then
term.setCursorPos(1000-xscroll,y)
term.write(" What ARE you doing? Stop messing around! ")
end
if 2016-yscroll == y then
term.setCursorPos(200-xscroll,y)
term.write(" Lines don't like to be intersected, you know. ")
end
if 2017-yscroll == y then
term.setCursorPos(200-xscroll,y)
term.write(" It makes them very crossed. ")
end
if 800-yscroll == y then
term.setCursorPos(1700-xscroll,y)
term.write(" You stare deeply into the void. ")
end
if 801-yscroll == y then
term.setCursorPos(1704-xscroll,y)
term.write(" And the void ")
end
if 802-yscroll == y then
term.setCursorPos(1704-xscroll,y)
term.write(" stares back. ")
end
--Is this the end?--
if (xscroll > ((screenEdges[1]*2)-scr_x)) then
for y = 1, scr_y do
if y+yscroll <= (screenEdges[2]*3) then
if not (y == scr_y and doRenderBar == 1) then
term.setCursorPos((screenEdges[1]+1)-(xscroll-screenEdges[1]),y)
term.write("@")
end
end
end
end
if (xscroll > (screenEdges[1]-scr_x)) then --regular limit
for y = 1, scr_y do
if y+yscroll <= screenEdges[2] then
if not (y == scr_y and doRenderBar == 1) then
term.setCursorPos((screenEdges[1]+1)-xscroll,y)
term.write("@")
end
end
end
end
end
--render areas that won't save
if xscroll < 0 then
for y = 1, scr_y do
if not (y == scr_y and doRenderBar == 1) then
term.setCursorPos(1,y)
term.write(badchar:rep(-xscroll))
end
end
end
if yscroll < 0 then
for y = 1, -yscroll do
if not (y == scr_y and doRenderBar == 1) then
term.setCursorPos(1,y)
term.write(badchar:rep(scr_x))
end
end
end
else
for y = 1, scr_y - yadjust do
term.setCursorPos(1,y)
term.clearLine()
end
end
end
importFromPaint = function(theInput)
local output = {}
local input
if type(theInput) == "string" then
input = explode("\n",theInput)
else
input = {}
for y = 1, #theInput do
input[y] = ""
for x = 1, #theInput[y] do
input[y] = input[y]..(CTB(theInput[y][x]) or " ")
end
end
end
for a = 1, #input do
line = input[a]
for b = 1, #line do
if (line:sub(b,b) ~= " ") and BTC(line:sub(b,b)) then
output[#output+1] = {
x = b,
y = a,
t = colors.white,
b = BTC(line:sub(b,b)) or colors.black,
c = " ",
}
end
end
end
return output
end
local lddfm = {
scroll = 0,
ypaths = {}
}
lddfm.scr_x, lddfm.scr_y = term.getSize()
lddfm.setPalate = function(_p)
if type(_p) ~= "table" then
_p = {}
end
lddfm.p = { --the DEFAULT color palate
bg = _p.bg or colors.gray, -- whole background color
d_txt = _p.d_txt or colors.yellow, -- directory text color
d_bg = _p.d_bg or colors.gray, -- directory bg color
f_txt = _p.f_txt or colors.white, -- file text color
f_bg = _p.f_bg or colors.gray, -- file bg color
p_txt = _p.p_txt or colors.black, -- path text color
p_bg = _p.p_bg or colors.lightGray, -- path bg color
close_txt = _p.close_txt or colors.gray, -- close button text color
close_bg = _p.close_bg or colors.lightGray,-- close button bg color
scr = _p.scr or colors.lightGray, -- scrollbar color
scrbar = _p.scrbar or colors.gray, -- scroll tab color
}
end
lddfm.setPalate()
lddfm.foldersOnTop = function(floop,path)
local output = {}
for a = 1, #floop do
if fs.isDir(fs.combine(path,floop[a])) then
table.insert(output,1,floop[a])
else
table.insert(output,floop[a])
end
end
return output
end
lddfm.filterFileFolders = function(list,path,_noFiles,_noFolders,_noCD,_doHidden)
local output = {}
for a = 1, #list do
local entry = fs.combine(path,list[a])
if fs.isDir(entry) then
if entry == ".." then
if not (_noCD or _noFolders) then table.insert(output,list[a]) end
else
if not ((not _doHidden) and list[a]:sub(1,1) == ".") then
if not _noFolders then table.insert(output,list[a]) end
end
end
else
if not ((not _doHidden) and list[a]:sub(1,1) == ".") then
if not _noFiles then table.insert(output,list[a]) end
end
end
end
return output
end
lddfm.isColor = function(col)
for k,v in pairs(colors) do
if v == col then
return true, k
end
end
return false
end
lddfm.clearLine = function(x1,x2,_y,_bg,_char)
local cbg, bg = term.getBackgroundColor()
local x,y = term.getCursorPos()
local sx,sy = term.getSize()
if type(_char) == "string" then char = _char else char = " " end
if type(_bg) == "number" then
if lddfm.isColor(_bg) then bg = _bg
else bg = cbg end
else bg = cbg end
term.setCursorPos(x1 or 1, _y or y)
term.setBackgroundColor(bg)
if x2 then --it pains me to add an if statement to something as simple as this
term.write((char or " "):rep(x2-x1))
else
term.write((char or " "):rep(sx-(x1 or 0)))
end
term.setBackgroundColor(cbg)
term.setCursorPos(x,y)
end
lddfm.render = function(_x1,_y1,_x2,_y2,_rlist,_path,_rscroll,_canClose,_scrbarY)
local px,py = term.getCursorPos()
plc.tsv(false)
local x1, x2, y1, y2 = _x1 or 1, _x2 or lddfm.scr_x, _y1 or 1, _y2 or lddfm.scr_y
local rlist = _rlist or {"Invalid directory."}
local path = _path or "And that's terrible."
ypaths = {}
local rscroll = _rscroll or 0
for a = y1, y2 do
lddfm.clearLine(x1,x2,a,lddfm.p.bg)
end
term.setCursorPos(x1,y1)
term.setTextColor(lddfm.p.p_txt)
lddfm.clearLine(x1,x2+1,y1,lddfm.p.p_bg)
term.setBackgroundColor(lddfm.p.p_bg)
term.write(("/"..path):sub(1,x2-x1))
for a = 1,(y2-y1) do
if rlist[a+rscroll] then
term.setCursorPos(x1,a+(y1))
if fs.isDir(fs.combine(path,rlist[a+rscroll])) then
lddfm.clearLine(x1,x2,a+(y1),lddfm.p.d_bg)
term.setTextColor(lddfm.p.d_txt)
term.setBackgroundColor(lddfm.p.d_bg)
else
lddfm.clearLine(x1,x2,a+(y1),lddfm.p.f_bg)
term.setTextColor(lddfm.p.f_txt)
term.setBackgroundColor(lddfm.p.f_bg)
end
term.write(rlist[a+rscroll]:sub(1,x2-x1))
ypaths[a+(y1)] = rlist[a+rscroll]
else
lddfm.clearLine(x1,x2,a+(y1),lddfm.p.bg)
end
end
local scrbarY = _scrbarY or math.ceil( (y1+1)+( (_rscroll/(#_rlist-(y2-(y1+1))))*(y2-(y1+1)) ) )
for a = y1+1, y2 do
term.setCursorPos(x2,a)
if a == scrbarY then
term.setBackgroundColor(lddfm.p.scrbar)
else
term.setBackgroundColor(lddfm.p.scr)
end
term.write(" ")
end
if _canClose then
term.setCursorPos(x2-4,y1)
term.setTextColor(lddfm.p.close_txt)
term.setBackgroundColor(lddfm.p.close_bg)
term.write("close")
end
term.setCursorPos(px,py)
plc.tsv(true)
return scrbarY
end
lddfm.coolOutro = function(x1,y1,x2,y2,_bg,_txt,char)
local cx, cy = term.getCursorPos()
local bg, txt = term.getBackgroundColor(), term.getTextColor()
term.setTextColor(_txt or colors.white)
term.setBackgroundColor(_bg or colors.black)
local _uwah = 0
for y = y1, y2 do
for x = x1, x2 do
_uwah = _uwah + 1
term.setCursorPos(x,y)
term.write(char or " ")
if _uwah >= math.ceil((x2-x1)*1.63) then sleep(0.05) _uwah = 0 end
end
end
term.setTextColor(txt)
term.setBackgroundColor(bg)
term.setCursorPos(cx,cy)
end
lddfm.scrollMenu = function(amount,list,y1,y2)
if #list >= y2-y1 then
lddfm.scroll = lddfm.scroll + amount
if lddfm.scroll < 0 then
lddfm.scroll = 0
end
if lddfm.scroll > #list-(y2-y1) then
lddfm.scroll = #list-(y2-y1)
end
end
end
lddfm.makeMenu = function(_x1,_y1,_x2,_y2,_path,_noFiles,_noFolders,_noCD,_noSelectFolders,_doHidden,_p,_canClose)
if _noFiles and _noFolders then
return false, "C'mon, man..."
end
if _x1 == true then
return false, "arguments: x1, y1, x2, y2, path, noFiles, noFolders, noCD, noSelectFolders, doHidden, palate, canClose" -- a little help
end
lddfm.setPalate(_p)
local path, list = _path or ""
lddfm.scroll = 0
local _pbg, _ptxt = term.getBackgroundColor(), term.getTextColor()
local x1, x2, y1, y2 = _x1 or 1, _x2 or lddfm.scr_x, _y1 or 1, _y2 or lddfm.scr_y
local keysDown = {}
local _barrY
while true do
list = lddfm.foldersOnTop(lddfm.filterFileFolders(fs.list(path),path,_noFiles,_noFolders,_noCD,_doHidden),path)
if (fs.getDir(path) ~= "..") and not (_noCD or _noFolders) then
table.insert(list,1,"..")
end
_res, _barrY = pcall( function() return lddfm.render(x1,y1,x2,y2,list,path,lddfm.scroll,_canClose) end)
if not _res then
plc.tsv(true)
error(_barrY)
end
local evt = {os.pullEvent()}
if evt[1] == "mouse_scroll" then
lddfm.scrollMenu(evt[2],list,y1,y2)
elseif evt[1] == "mouse_click" then
local butt,mx,my = evt[2],evt[3],evt[4]
if (butt == 1 and my == y1 and mx <= x2 and mx >= x2-4) and _canClose then
--lddfm.coolOutro(x1,y1,x2,y2)
term.setTextColor(_ptxt) term.setBackgroundColor(_pbg)
return false
elseif ypaths[my] and (mx >= x1 and mx < x2) then --x2 is reserved for the scrollbar, breh
if fs.isDir(fs.combine(path,ypaths[my])) then
if _noCD or butt == 3 then
if not _noSelectFolders or _noFolders then
--lddfm.coolOutro(x1,y1,x2,y2)
term.setTextColor(_ptxt) term.setBackgroundColor(_pbg)
return fs.combine(path,ypaths[my])
end
else
path = fs.combine(path,ypaths[my])
lddfm.scroll = 0
end
else
term.setTextColor(_ptxt) term.setBackgroundColor(_pbg)
return fs.combine(path,ypaths[my])
end
end
elseif evt[1] == "key" then
keysDown[evt[2]] = true
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.
--lddfm.coolOutro(x1,y1,x2,y2)
term.setTextColor(_ptxt) term.setBackgroundColor(_pbg)
return path
end
if evt[2] == keys.up then
lddfm.scrollMenu(-1,list,y1,y2)
elseif evt[2] == keys.down then
lddfm.scrollMenu(1,list,y1,y2)
end
if evt[2] == keys.pageUp then
lddfm.scrollMenu(y1-y2,list,y1,y2)
elseif evt[2] == keys.pageDown then
lddfm.scrollMenu(y2-y1,list,y1,y2)
end
if evt[2] == keys.home then
lddfm.scroll = 0
elseif evt[2] == keys["end"] then
if #list > (y2-y1) then
lddfm.scroll = #list-(y2-y1)
end
end
if evt[2] == keys.h then
if keysDown[keys.leftCtrl] or keysDown[keys.rightCtrl] then
_doHidden = not _doHidden
end
elseif _canClose and (evt[2] == keys.x or evt[2] == keys.q or evt[2] == keys.leftCtrl) then
--lddfm.coolOutro(x1,y1,x2,y2)
term.setTextColor(_ptxt) term.setBackgroundColor(_pbg)
return false
end
elseif evt[1] == "key_up" then
keysDown[evt[2]] = false
end
end
end
local getBlittle = function()
if not blittle then
if fs.exists(fs.combine(plc.apipath,"blittle")) then
os.loadAPI(fs.combine(plc.apipath,"blittle"))
if not blittleTerm then
blittleTerm = blittle.createWindow()
end
return blittleTerm, firstTerm
else
local geet = http.get("http://pastebin.com/raw/ujchRSnU")
if not geet then
return false
else
geet = geet.readAll()
local file = fs.open(fs.combine(plc.apipath,"blittle"),"w")
file.write(geet)
file.close()
os.loadAPI(fs.combine(plc.apipath,"blittle"))
--fs.delete(plc.apipath)
if not blittleTerm then
blittleTerm = blittle.createWindow()
end
return blittleTerm, firstTerm
end
end
else
if not blittleTerm then
blittleTerm = blittle.createWindow()
end
return blittleTerm, firstTerm
end
end
local getUCG = function()
if not ucg then
if fs.exists(fs.combine(plc.apipath,"ucg")) then
os.loadAPI(fs.combine(plc.apipath,"ucg"))
return true
else
local geet = http.get("https://raw.githubusercontent.com/ardera/libucg/master/src/libucg")
if not geet then
return false
else
geet = geet.readAll()
local file = fs.open(fs.combine(plc.apipath,"ucg"),"w")
file.write(geet)
file.close()
os.loadAPI(fs.combine(plc.apipath,"ucg"))
end
end
end
end
local getBBPack = function()
if not bbpack then
if fs.exists(fs.combine(plc.apipath,"bbpack")) then
os.loadAPI(fs.combine(plc.apipath,"bbpack"))
return true
else
local geet = http.get("https://pastebin.com/raw/cUYTGbpb")
if not geet then
return false
else
geet = geet.readAll()
local file = fs.open(fs.combine(plc.apipath,"bbpack"),"w")
file.write(geet)
file.close()
os.loadAPI(fs.combine(plc.apipath,"bbpack"))
end
end
end
end
local getGIF = function()
getBBPack()
if not GIF then
if fs.exists(fs.combine(plc.apipath,"GIF")) then
os.loadAPI(fs.combine(plc.apipath,"GIF"))
return true
else
local geet = http.get("https://pastebin.com/raw/5uk9uRjC")
if not geet then
return false
else
geet = geet.readAll()
local file = fs.open(fs.combine(plc.apipath,"GIF"),"w")
file.write(geet)
file.close()
os.loadAPI(fs.combine(plc.apipath,"GIF"))
end
end
end
end
local NFPserializeImage = function(str)
local bepis = explode("\n",str)
local output = {}
for y = 1, #bepis do
output[y] = {}
for x = 1, #bepis[y] do
output[y][x] = BTC(bepis[y]:sub(x,x),true)
end
end
return textutils.unserialize(textutils.serialize(output):gsub("\n",""):gsub(" ",""):gsub(",}","}"))
end
local importFromGIF = function(filename,verbose)
getGIF()
local output = {}
local image
local rawGif = GIF.loadGIF(filename)
if painconfig.useFlattenGIF then
if verbose then
print("Flattening...")
end
rawGif = GIF.flattenGIF(rawGif)
sleep(0.05)
end
local cx, cy = term.getCursorPos()
for a = 1, #rawGif do
output[a] = importFromPaint(GIF.toPaintutils(rawGif[a]))
if verbose then
term.setCursorPos(cx,cy)
write("Did "..a.."/"..#rawGif.." ")
end
if a % 1 then sleep(0.05) end --used to be a % 2, might change later
end
return output
end
local exportToPaint
local exportToGIF = function(input)
getGIF()
local outGIF = {}
for a = 1, #paintEncoded do
outGIF[a] = NFPserializeImage(exportToPaint(paintEncoded[a]))
sleep(0.05)
end
if painconfig.useFlattenGIF then
return GIF.flattenGIF(GIF.buildGIF(table.unpack(outGIF)),true)
else
return GIF.buildGIF(table.unpack(outGIF))
end
end
local importFromUCG = function(filename)
getUCG()
return importFromPaint(ucg.readFile(filename))
end
local exportToUCG = function(filename, input)
getUCG()
ucg.writeFile(filename, NFPserializeImage(exportToPaint(input)))
end
renderPAIN = function(dots,xscroll,yscroll,doPain,dontRenderBar)
plc.tsv(false)
local beforeTX,beforeBG = term.getTextColor(), term.getBackgroundColor()
local cx,cy = term.getCursorPos()
local FUCK, SHIT = pcall(function()
if doPain then
if (not plc.renderBlittle) then
if not dontRenderBar then
renderBar(barmsg,true)
end
renderPainyThings(xscroll,yscroll,plc.evenDrawGrid)
else
term.clear()
end
end
for a = 1, #dots do
local d = dots[a]
if doPain then
if not ((d.y-yscroll >= 1 and d.y-yscroll <= scr_y-(plc.renderBlittle and 0 or doRenderBar)) and (d.x-xscroll >= 1 and d.x-xscroll <= scr_x)) then
d = nil
end
end
if d then
term.setCursorPos(d.x-(xscroll or 0),d.y-(yscroll or 0))
term.setBackgroundColor((paint.doGray and grayOut(d.b) or d.b) or rendback.b)
if painconfig.gridBleedThrough then
term.setTextColor(rendback.t)
term.write((d.x >= 1 and d.y >= 1) and grid[ ro( d.y+2, #grid)+1]:sub(1+ro(d.x+-1,#grid[1]), 1+ro(d.x+-1,#grid[1])) or "/")
else
term.setTextColor( (paint.doGray and grayOut(d.t) or d.t) or rendback.t)
term.write(d.c or " ")
end
end
end
end)
term.setBackgroundColor(beforeBG or rendback.b)
term.setTextColor(beforeTX or rendback.t)
term.setCursorPos(cx,cy)
plc.tsv(true)
if not FUCK then error(SHIT) end --GOD DAMN IT
end
renderPAINFS = function(filename,xscroll,yscroll,frameNo,doPain)
local file = fs.open(filename,"r")
local contents = file.readAll()
local amntFrames
file.close()
local tcontents = textutils.unserialize(contents)
if type(tcontents) ~= "table" then
tcontents = importFromPaint(contents)
else
amntFrames = #tcontents
tcontents = tcontents[frameNo or 1]
end
renderPAIN(tcontents,xscroll,yscroll,doPain)
return amntFrames
end
local putDotDown = function(dot) -- only 'x' and 'y' are required arguments
paintEncoded[frame][#paintEncoded[frame]+1] = {
x = dot.x + paint.scrollX,
y = dot.y + paint.scrollY,
c = dot.c or paint.c,
b = dot.b or (swapColors and paint.t or paint.b),
t = dot.t or (swapColors and paint.b or paint.t),
m = dot.m or paint.m,
}
end
local saveToUndoBuffer = function()
if plc.undoPos < #plc.undoBuffer then
for a = #plc.undoBuffer, plc.undoPos + 1, -1 do
table.remove(plc.undoBuffer, a)
end
end
if plc.undoPos >= painconfig.undoBufferSize then
for a = 2, #plc.undoBuffer do
plc.undoBuffer[a - 1] = plc.undoBuffer[a]
end
plc.undoBuffer[#plc.undoBuffer] = deepCopy(paintEncoded)
else
plc.undoPos = plc.undoPos + 1
plc.undoBuffer[plc.undoPos] = deepCopy(paintEncoded)
end
end
local doUndo = function()
plc.undoPos = math.max(1, plc.undoPos - 1)
paintEncoded = deepCopy(plc.undoBuffer[plc.undoPos])
if not paintEncoded[frame] then
frame = #paintEncoded
end
end
local doRedo = function()
plc.undoPos = math.min(#plc.undoBuffer, plc.undoPos + 1)
paintEncoded = deepCopy(plc.undoBuffer[plc.undoPos])
if not paintEncoded[frame] then
frame = #paintEncoded
end
end
local putDownText = function(x,y)
term.setCursorPos(x,y)
term.setTextColor((paint.doGray and grayOut(paint.t or rendback.t)) or (paint.t or rendback.t))
term.setBackgroundColor((paint.doGray and grayOut(paint.b or rendback.b)) or (paint.b or rendback.b))
local msg = read()
if #msg > 0 then
for a = 1, #msg do
putDotDown({x=(x+a)-1, y=y, c=msg:sub(a,a)})
end
end
saveToUndoBuffer()
end
local deleteDot = function(x,y) --deletes all dots at point x,y
local good = false
for a = #paintEncoded[frame],1,-1 do
local b = paintEncoded[frame][a]
if (x == b.x) and (y == b.y) then
table.remove(paintEncoded[frame],a)
good = true
end
end
return good
end
exportToPaint = function(input,noTransparent) --exports paintEncoded frame to regular paint format. input is expected to be paintEncoded[frame]
local doopTXT, doopTXCOL, doopBGCOL = {}, {}, {}
local p = input
local pheight = 0
local pwidth = 0
for a = 1, #p do
if p[a].y > pheight then
pheight = p[a].y
end
if p[a].x > pwidth then
pwidth = p[a].x
end
end
for k,v in pairs(p) do
if not doopBGCOL[v.y] then
doopBGCOL[v.y] = {}
doopTXCOL[v.y] = {}
doopTXT[v.y] = {}
end
doopBGCOL[v.y][v.x] = CTB(v.b)
doopTXCOL[v.y][v.x] = CTB(v.t)
doopTXT[v.y][v.x] = v.c
end
local nfpoutputTXT, nfpoutputTXCOL, nfpoutputBGCOL = "", "", ""
for y = 1, pheight do
if doopBGCOL[y] then
for x = 1, pwidth do
if doopBGCOL[y][x] then
nfpoutputBGCOL = nfpoutputBGCOL..doopBGCOL[y][x]
nfpoutputTXCOL = nfpoutputTXCOL..doopTXCOL[y][x]
nfpoutputTXT = nfpoutputTXT..(((doopTXT[y][x] == " " and noTransparent) and "\128" or doopTXT[y][x]) or " ")
else
nfpoutputBGCOL = nfpoutputBGCOL..(noTransparent and "0" or " ")
nfpoutputTXCOL = nfpoutputTXCOL..(noTransparent and "0" or " ")
nfpoutputTXT = nfpoutputTXT.." "
end
end
end
if y ~= pheight then
nfpoutputBGCOL = nfpoutputBGCOL.."\n"
nfpoutputTXCOL = nfpoutputTXCOL.."\n"
nfpoutputTXT = nfpoutputTXT.."\n"
end
end
return nfpoutputBGCOL, pheight, pwidth
end
local exportToNFT = function(input)
local bgcode, txcode = "\30", "\31"
local output = ""
local text, back
local doot, pheight, pwidths = tableFormatPE(input)
for y = 1, pheight do
text, back = "0", "f"
if doot[y] then
for x = 1, pwidths[y] do
if doot[y][x] then
if doot[y][x].back ~= back then
back = doot[y][x].back
output = output .. bgcode .. back
end
if doot[y][x].text ~= text then
text = doot[y][x].text
output = output .. txcode .. text
end
output = output .. doot[y][x].char
else
output = output .. " "
end
end
end
if y < pheight then
output = output .. "\n"
end
end
return output
end
local importFromNFT = function(input) --imports NFT formatted string image to paintEncoded[frame] formatted table image. please return a paintEncoded[frame] formatted table.
local tinput = explode("\n",input)
local tcol,bcol
local cx --represents the x position in the picture
local sx --represents the x position in the file
local output = {}
for y = 1, #tinput do
tcol,bcol = colors.white,colors.black
cx, sx = 1, 0
while sx < #tinput[y] do
sx = sx + 1
if tinput[y]:sub(sx,sx) == "\30" then
bcol = BTC(tinput[y]:sub(sx+1,sx+1))
sx = sx + 1
elseif tinput[y]:sub(sx,sx) == "\31" then
tcol = BTC(tinput[y]:sub(sx+1,sx+1))
sx = sx + 1
else
if tcol and bcol then
output[#output+1] = {
["x"] = cx,
["y"] = y,
["b"] = bcol,
["t"] = tcol,
["c"] = tinput[y]:sub(sx,sx),
["m"] = 0,
}
end
cx = cx + 1
end
end
end
return output
end
exportToBLT = function(input,filename,doAllFrames,noSave)
local output = {}
local thisImage,pheight,pwidth,nfpinput
getBlittle()
for a = doAllFrames and 1 or frame, doAllFrames and #input or frame do
output[#output+1] = blittle.shrink(NFPserializeImage(exportToPaint(input[a]),true),colors.black)
end
if #output == 1 then output = output[1] end
if not noSave then
blittle.save(output,filename)
end
return output
end
importFromBLT = function(input) --takes in filename, not contents
local output = {}
getBlittle()
local wholePic = blittle.load(input)
if wholePic.height then wholePic = {wholePic} end
local image
for a = 1, #wholePic do
image = wholePic[a]
output[#output+1] = {}
for y = 1, image.height*3 do
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
output[#output][#output[#output]+1] = {
m = 0,
x = x,
y = y,
t = BTC((image[2][math.ceil(y/3)]:sub(math.ceil(x/2),math.ceil(x/2)).."0"):sub(1,1)),
b = BTC((image[3][math.ceil(y/3)]:sub(math.ceil(x/2),math.ceil(x/2)).."0"):sub(1,1)),
c = BTC((image[1][math.ceil(y/3)]:sub(math.ceil(x/2),math.ceil(x/2)).." "):sub(1,1)),
}
end
end
end
return output
end
local getTheDoots = function(pe)
local hasBadDots = false
local baddestX,baddestY = 1,1
for b = 1, #pe do
local doot = pe[b]
if doot.x <= 0 or doot.y <= 0 then
hasBadDots = true
if doot.x < baddestX then
baddestX = doot.x
end
if doot.y < baddestY then
baddestY = doot.y
end
end
if b % 64 == 0 then yield() end
end
return baddestX, baddestY
end
local checkBadDots = function()
local hasBadDots = false
for a = 1, #paintEncoded do
local radx,rady = getTheDoots(paintEncoded[a])
if radx ~= 1 or rady ~= 1 then
hasBadDots = true
end
end
if hasBadDots then
local ting = bottomPrompt("Dot(s) are OoB! Save or fix? (Y/N/F)",_,"ynf",{keys.leftCtrl,keys.rightCtrl})
if ting == "f" then
for a = 1, #paintEncoded do
local baddestX, baddestY = getTheDoots(paintEncoded[a])
paintEncoded[a] = movePaintEncoded(paintEncoded[a],-(baddestX-1),-(baddestY-1))
end
elseif ting ~= "y" then
barmsg = ""
return false
end
end
end
local convertToGrayscale = function(pe)
local output = pe
for a = 1, #pe do
for b = 1, #pe[a] do
output[a][b].b = grayOut(pe[a][b].b)
output[a][b].t = grayOut(pe[a][b].t)
if not output[a][b].m then output[a][b].m = 1 end
end
if a % 2 == 0 then yield() end
end
return output
end
local reRenderPAIN = function(overrideRenderBar)
local _reallyDoRenderBar = doRenderBar
-- doRenderBar = 1
renderPAIN(paintEncoded[frame],paint.scrollX,paint.scrollY,true,overrideRenderBar)
doRenderBar = _reallyDoRenderBar
end
local fillTool = function(_frame,cx,cy,dot,isDeleting) -- "_frame" is the frame NUMBER
local maxX, maxY = 1, 1
local minX, minY = 1, 1
paintEncoded = clearAllRedundant(paintEncoded)
local frame = paintEncoded[_frame]
local scx, scy = cx+paint.scrollX, cy+paint.scrollY
local output = {}
for a = 1, #frame do
maxX = math.max(maxX, frame[a].x)
maxY = math.max(maxY, frame[a].y)
minX = math.min(minX, frame[a].x)
minY = math.min(minY, frame[a].y)
end
maxX = math.max(maxX, scx)
maxY = math.max(maxY, scy)
minX = math.min(minX, scx)
minY = math.min(minY, scy)
maxX = math.max(maxX, screenEdges[1])
maxY = math.max(maxY, screenEdges[2])
local doop = {}
local touched = {}
local check = {[scy] = {[scx] = true}}
for y = minY, maxY do
doop[y] = {}
touched[y] = {}
for x = minX, maxX do
doop[y][x] = {
c = " ",
b = 0,
t = 0
}
touched[y][x] = false
end
end
for a = 1, #frame do
doop[frame[a].y][frame[a].x] = {
c = frame[a].c,
t = frame[a].t,
b = frame[a].b
}
end
local initDot = {
c = doop[scy][scx].c,
t = doop[scy][scx].t,
b = doop[scy][scx].b
}
local chkpos = function(x, y, checkList)
if (x < minX or x > maxX) or (y < minY or y > maxY) then
return false
else
if (doop[y][x].b ~= initDot.b) or (doop[y][x].t ~= initDot.t and doop[y][x].c ~= " ") or (doop[y][x].c ~= initDot.c) then
return false
end
if check[y] then
if check[y][x] then
return false
end
end
if touched[y][x] then
return false
end
return true
end
end
local doBreak
local step = 0
local currentlyOnScreen
while true do
doBreak = true
for chY, v in pairs(check) do
for chX, isTrue in pairs(v) do
currentlyOnScreen = (chX-paint.scrollX >= 1 and chX-paint.scrollX <= scr_x and chY-paint.scrollY >= 1 and chY-paint.scrollY <= scr_y)
if isTrue and (not touched[chY][chX]) then
step = step + 1
if painconfig.doFillAnimation then
if currentlyOnScreen then
reRenderPAIN(true)
end
end
if isDeleting then
deleteDot(chX, chY)
else
frame[#frame+1] = {
x = chX,
y = chY,
c = dot.c,
t = dot.t,
b = dot.b
}
end
touched[chY][chX] = true
-- check adjacent
if chkpos(chX+1, chY) then
check[chY][chX+1] = true
doBreak = false
end
if chkpos(chX-1, chY) then
check[chY][chX-1] = true
doBreak = false
end
if chkpos(chX, chY+1) then
check[chY+1] = check[chY+1] or {}
check[chY+1][chX] = true
doBreak = false
end
if chkpos(chX, chY-1) then
check[chY-1] = check[chY-1] or {}
check[chY-1][chX] = true
doBreak = false
end
-- check diagonal
if painconfig.doFillDiagonal then
if chkpos(chX-1, chY-1) then
check[chY-1] = check[chY-1] or {}
check[chY-1][chX-1] = true
doBreak = false
end
if chkpos(chX+1, chY-1) then
check[chY-1] = check[chY-1] or {}
check[chY-1][chX+1] = true
doBreak = false
end
if chkpos(chX-1, chY+1) then
check[chY+1] = check[chY+1] or {}
check[chY+1][chX-1] = true
doBreak = false
end
if chkpos(chX+1, chY+1) then
check[chY+1] = check[chY+1] or {}
check[chY+1][chX+1] = true
doBreak = false
end
end
if step % ((painconfig.doFillAnimation and currentlyOnScreen) and 4 or 1024) == 0 then -- tries to prevent crash
sleep(0.05)
end
end
end
end
if doBreak then
break
end
end
paintEncoded = clearAllRedundant(paintEncoded)
end
local boxCharSelector = function()
local co = function(pos)
if pos then
term.setTextColor(colors.lime)
term.setBackgroundColor(colors.green)
else
term.setTextColor(colors.lightGray)
term.setBackgroundColor(colors.gray)
end
end
local rend = function()
term.setCursorPos(scr_x - 3, scr_y)
term.setBackgroundColor(colors.lightGray)
term.setTextColor(colors.gray)
term.clearLine()
term.write("\\" .. string.byte(getDrawingCharacter(boxchar.topLeft, boxchar.topRight, boxchar.left, boxchar.right, boxchar.bottomLeft, boxchar.bottomRight).char) .. " ")
term.setTextColor(colors.black)
term.write("Press CTRL or 'N' when ready.")
term.setCursorPos(1,scr_y-3) co(boxchar.topLeft) write("Q") co(boxchar.topRight) write("W")
term.setCursorPos(1,scr_y-2) co(boxchar.left) write("A") co(boxchar.right) write("S")
term.setCursorPos(1,scr_y-1) co(boxchar.bottomLeft) write("Z") co(boxchar.bottomRight) write("X")
end
while true do
rend()
local evt = {os.pullEvent()}
if evt[1] == "key" then
local key = evt[2]
if key == keys.leftCtrl or key == keys.rightCtrl or key == keys.n then
break
else
if key == keys.q then boxchar.topLeft = not boxchar.topLeft end
if key == keys.w then boxchar.topRight = not boxchar.topRight end
if key == keys.a then boxchar.left = not boxchar.left end
if key == keys.s then boxchar.right = not boxchar.right end
if key == keys.z then boxchar.bottomLeft = not boxchar.bottomLeft end
if key == keys.x then boxchar.bottomRight = not boxchar.bottomRight end
end
elseif evt[1] == "mouse_click" or evt[1] == "mouse_drag" then
local button, mx, my = evt[2], evt[3], evt[4]
if my >= scr_y-3 then
if mx == 1 then
if my == scr_y - 3 then boxchar.topLeft = not boxchar.topLeft end
if my == scr_y - 2 then boxchar.left = not boxchar.left end
if my == scr_y - 1 then boxchar.bottomLeft = not boxchar.bottomLeft end
elseif mx == 2 then
if my == scr_y - 3 then boxchar.topRight = not boxchar.topRight end
if my == scr_y - 2 then boxchar.right = not boxchar.right end
if my == scr_y - 1 then boxchar.bottomRight = not boxchar.bottomRight end
elseif evt[1] == "mouse_click" then
break
end
elseif evt[1] == "mouse_click" then
break
end
end
end
if boxchar.topLeft and boxchar.topRight and boxchar.left and boxchar.right and boxchar.bottomLeft and boxchar.bottomRight then
swapColors = false
return " "
else
local output = getDrawingCharacter(boxchar.topLeft, boxchar.topRight, boxchar.left, boxchar.right, boxchar.bottomLeft, boxchar.bottomRight)
swapColors = not output.inverted
return output.char
end
end
local specialCharSelector = function()
local chars = {}
local buff = 0
for y = 1, 16 do
for x = 1, 16 do
chars[y] = chars[y] or {}
chars[y][x] = string.char(buff)
buff = buff + 1
end
end
local sy = scr_y - (#chars + 1)
local char = paint.c
local render = function()
for y = 1, #chars do
for x = 1, #chars do
term.setCursorPos(x,y+sy)
if chars[y][x] == char then
term.blit(chars[y][x], "5", "d")
else
term.blit(chars[y][x], "8", "7")
end
end
end
end
local evt, butt, x, y
render()
term.setCursorPos(1,scr_y)
term.setBackgroundColor(colors.lightGray)
term.setTextColor(colors.black)
term.clearLine()
term.write("Press CTRL or 'N' when ready.")
while true do
term.setCursorPos(scr_x - 3, scr_y)
term.setTextColor(colors.gray)
term.write("\\" .. string.byte(char) .. " ")
evt, butt, x, y = os.pullEvent()
if (evt == "mouse_click" or evt == "mouse_drag") then
if chars[y-sy] then
if chars[y-sy][x] then
if (chars[y-sy][x] ~= char) then
char = chars[y-sy][x]
render()
end
elseif evt == "mouse_click" then
return char
end
elseif evt == "mouse_click" then
return char
end
elseif evt == "key" then
if (butt == keys.n) or (butt == keys.leftCtrl) then
return char
end
end
end
end
local dontDragThisTime = false
local resetInputState = function()
miceDown = {}
keysDown = {}
isDragging = false
dontDragThisTime = true
end
local gotoCoords = function()
local newX = bottomPrompt("Goto X:")
newX = tonumber(newX)
local newY
if newX then
newY = bottomPrompt("Goto Y:")
newY = tonumber(newY)
paint.scrollX = newX or paint.scrollX
paint.scrollY = newY or paint.scrollY
end
end
local renderAllPAIN = function()
renderPAIN(paintEncoded[frame],paint.scrollX,paint.scrollY,true)
end
local checkIfNFP = function(str) --does not check table format, only string format
local good = {
['0'] = true,
['1'] = true,
['2'] = true,
['3'] = true,
['4'] = true,
['5'] = true,
['6'] = true,
['7'] = true,
['8'] = true,
['9'] = true,
a = true,
b = true,
c = true,
d = true,
e = true,
f = true,
[" "] = true,
["\n"] = true
}
for a = 1, #str do
if not good[str:sub(a,a):lower()] then
return false
end
end
return true
end
local selectRegion = function()
local mevt, id, x1, y1 = os.pullEvent("mouse_click")
local x2, y2, pos, redrawID
local renderRectangle = true
redrawID = os.startTimer(0.5)
while true do
mevt, id, x2, y2 = os.pullEvent()
if mevt == "mouse_up" or mevt == "mouse_drag" or mevt == "mouse_click" then
pos = {{
x1 < x2 and x1 or x2,
y1 < y2 and y1 or y2
},{
x1 < x2 and x2 or x1,
y1 < y2 and y2 or y1
}}
end
if mevt == "mouse_up" then
break
end
if (mevt == "mouse_drag") or (mevt == "timer" and id == redrawID) then
renderAllPAIN()
if renderRectangle then
term.setTextColor(rendback.t)
term.setBackgroundColor(rendback.b)
for y = pos[1][2], pos[2][2] do
if y ~= scr_y then
term.setCursorPos(pos[1][1], y)
if (y == pos[1][2] or y == pos[2][2]) then
term.write(("#"):rep(1 + pos[2][1] - pos[1][1]))
else
term.write("#")
term.setCursorPos(pos[2][1], y)
term.write("#")
end
end
end
end
end
if (mevt == "timer" and id == redrawID) then
renderRectangle = not renderRectangle
redrawID = os.startTimer(0.25)
end
end
local output = {}
pos[1][1] = pos[1][1] + paint.scrollX
pos[2][1] = pos[2][1] + paint.scrollX
pos[1][2] = pos[1][2] + paint.scrollY
pos[2][2] = pos[2][2] + paint.scrollY
for k,v in pairs(paintEncoded[frame]) do
if v.x >= pos[1][1] and v.x <= pos[2][1] then
if v.y >= pos[1][2] and v.y <= pos[2][2] then
output[#output+1] = {
x = v.x - pos[1][1],
y = v.y - pos[1][2],
t = v.t,
c = v.c,
b = v.b,
m = v.m
}
end
end
end
return output, pos[1][1], pos[1][2], pos[2][1], pos[2][2]
end
local openNewFile = function(fname, allowNonImageNFP)
local file = fs.open(fname,"r")
local contents = file.readAll()
file.close()
if type(textutils.unserialize(contents)) ~= "table" then
term.setTextColor(colors.white)
if contents:sub(1,3) == "BLT" then --thank you bomb bloke for this easy identifier
if plc.pMode ~= 1 then print("Importing from BLT...") end
return importFromBLT(fname), 3
elseif contents:sub(1,3) == "GIF" then
if plc.pMode ~= 1 then print("Importing from GIF, this'll take a while...") end
return importFromGIF(fname,true), 5
elseif contents:sub(1,4) == "?!7\2" then
if plc.pMode ~= 1 then print("Importing from UCG...") end
return {importFromUCG(fname)}, 6
elseif contents:find(string.char(30)) and contents:find(string.char(31)) then
if plc.pMode ~= 1 then print("Importing from NFT...") end
return {importFromNFT(contents)}, 2
elseif (checkIfNFP(contents) or allowNonImageNFP) then
print("Importing from NFP...")
return {importFromPaint(contents)}, 1
else
return false, "That is not a valid image file."
end
else
return textutils.unserialize(contents), 4
end
end
local editFuncs = {
copy = function()
local board = bottomPrompt("Copy to board: ")
renderAllPAIN()
renderBottomBar("Select region to copy.")
local selectedDots = selectRegion()
theClipboard[board] = selectedDots
barmsg = "Copied to '"..board.."'"
doRender = true
keysDown = {}
miceDown = {}
end,
cut = function()
local board = bottomPrompt("Cut to board: ")
renderAllPAIN()
renderBottomBar("Select region to cut.")
local selectedDots, x1, y1, x2, y2 = selectRegion()
theClipboard[board] = selectedDots
local dot
for i = #paintEncoded[frame], 1, -1 do
dot = paintEncoded[frame][i]
if dot.x >= x1 and dot.x <= x2 then
if dot.y >= y1 and dot.y <= y2 then
table.remove(paintEncoded[frame], i)
end
end
end
barmsg = "Cut to '"..board.."'"
doRender = true
saveToUndoBuffer()
keysDown = {}
miceDown = {}
end,
paste = function()
local board = bottomPrompt("Paste from board: ")
renderAllPAIN()
renderBottomBar("Click to paste. (top left corner)")
if theClipboard[board] then
local mevt
repeat
mevt = {os.pullEvent()}
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-1)
for k,v in pairs(theClipboard[board]) do
paintEncoded[frame][#paintEncoded[frame]+1] = {
x = v.x + paint.scrollX + (mevt[3]),
y = v.y + paint.scrollY + (mevt[4]),
c = v.c,
t = v.t,
b = v.b,
m = v.m
}
end
paintEncoded[frame] = clearRedundant(paintEncoded[frame])
barmsg = "Pasted from '"..board.."'"
doRender = true
saveToUndoBuffer()
keysDown = {}
miceDown = {}
else
barmsg = "No such clipboard."
doRender = true
end
end
}
local blockEnlargeFrame = function(frameNo)
local frame = deepCopy(paintEncoded[frameNo])
local charConvert = {
["\129"] = {{true , false},{false, false},{false, false}},
["\130"] = {{false, true },{false, false},{false, false}},
["\131"] = {{true , true },{false, false},{false, false}},
["\132"] = {{false, false},{true , false},{false, false}},
["\133"] = {{true , false},{true , false},{false, false}},
["\134"] = {{false, true },{true , false},{false, false}},
["\135"] = {{true , true },{true , false},{false, false}},
["\136"] = {{false, false},{false, true },{false, false}},
["\137"] = {{true , false},{false, true },{false, false}},
["\138"] = {{false, true },{false, true },{false, false}},
["\139"] = {{true , true },{false, true },{false, false}},
["\140"] = {{false, false},{true , true },{false, false}},
["\141"] = {{true , false},{true , true },{false, false}},
["\142"] = {{false, true },{true , true },{false, false}},
["\143"] = {{true , true },{true , true },{false, false}},
["\144"] = {{false, false},{false, false},{true , false}},
["\145"] = {{true , false},{false, false},{true , false}},
["\146"] = {{false, true },{false, false},{true , false}},
["\147"] = {{true , true },{false, false},{true , false}},
["\148"] = {{false, false},{true , false},{true , false}},
["\149"] = {{true , false},{true , false},{true , false}},
["\150"] = {{false, true },{true , false},{true , false}},
["\151"] = {{true , true },{true , false},{true , false}},
["\152"] = {{false, false},{false, true },{true , false}},
["\153"] = {{true , false},{false, true },{true , false}},
["\154"] = {{false, true },{false, true },{true , false}},
["\155"] = {{true , true },{false, true },{true , false}},
["\156"] = {{false, false},{true , true },{true , false}},
["\157"] = {{true , false},{true , true },{true , false}},
["\158"] = {{false, true },{true , true },{true , false}},
["\159"] = {{true , true },{true , true },{true , false}},
}
local output, b = {}
for k, dot in pairs(frame) do
b = charConvert[dot.c] or {{false, false},{false, false},{false, false}}
for y = 1, #b do
for x = 1, #b[y] do
output[#output+1] = {
x = (dot.x - 1) * 2 + (x - 0),
y = (dot.y - 1) * 3 + (y - 0),
c = " ",
-- t = b[y][x] and dot.t or dot.b,
-- b = b[y][x] and dot.b or dot.t
t = b[y][x] and dot.b or dot.t,
b = b[y][x] and dot.t or dot.b
}
end
end
end
barmsg = "Grew image."
return output
end
local displayMenu = function()
doRender = false
menuOptions = {"File","Edit","Window","Set","About","Exit"}
local diss = " "..tableconcat(menuOptions," ")
local cleary = scr_y-math.floor(#diss/scr_x)
local fileSave = function()
checkBadDots()
local output = deepCopy(paintEncoded)
if paint.doGray then
output = convertToGrayscale(output)
end
if not plc.fileName then
renderBottomBar("Save as: ")
local fnguess = read()
if fs.isReadOnly(fnguess) then
barmsg = "'"..fnguess.."' is read only."
return false
elseif fnguess:gsub(" ","") == "" then
return false
elseif fs.isDir(fnguess) then
barmsg = "'"..fnguess.."' is already a directory."
return false
elseif #fnguess > 255 then
barmsg = "Filename is too long."
return false
else
plc.fileName = fnguess
end
end
saveFile(plc.fileName,output)
term.setCursorPos(9,scr_y)
return plc.fileName
end
local filePrint = function()
local usedDots, dot = {}, {}
for a = 1, #paintEncoded[frame] do
dot = paintEncoded[frame][a]
if dot.x > paint.scrollX and dot.x < (paint.scrollX + 25) and dot.y > paint.scrollX and dot.y < (paint.scrollY + 21) then
if dot.c ~= " " then
usedDots[dot.t] = usedDots[dot.t] or {}
usedDots[dot.t][#usedDots[dot.t]+1] = {
x = dot.x - paint.scrollX,
y = dot.y - paint.scrollY,
char = dot.c
}
end
end
end
local dyes = {
[1] = "bonemeal",
[2] = "orange dye",
[4] = "magenta dye",
[8] = "light blue dye",
[16] = "dandelion yellow",
[32] = "lime dye",
[64] = "pink dye",
[128] = "gray dye",
[256] = "light gray dye",
[512] = "cyan dye",
[1024] = "purple dye",
[2048] = "lapis lazuli",
[4096] = "cocoa beans",
[8192] = "cactus green",
[16384] = "rose red",
[32768] = "ink sac",
}
local printer = peripheral.find("printer")
if not printer then
barmsg = "No printer found."
return false
end
local page
for color, dotList in pairs(usedDots) do
term.setBackgroundColor(colors.black)
term.setTextColor((color == colors.black) and colors.gray or color)
term.clear()
cwrite("Please insert "..dyes[color].." into the printer.", nil, math.floor(scr_y/2))
term.setTextColor(colors.lightGray)
cwrite("Then, press spacebar.", nil, math.floor(scr_y/2) + 1)
local evt
sleep(0.05)
repeat
evt = {os.pullEvent("key")}
until evt[2] == keys.space
page = page or printer.newPage()
if not page then
barmsg = "Check ink/paper."
return
end
for k,v in pairs(usedDots[color]) do
printer.setCursorPos(v.x, v.y)
printer.write(v.char)
end
end
printer.endPage()
barmsg = "Printed."
end
local fileExport = function(menuX,getRightToIt,_fileName)
local exportMode
if not tonumber(getRightToIt) then
exportMode = makeSubMenu(menuX or 8,scr_y-2,{"Paint","NFT","BLT","PAIN Native","GIF","UCG"})
else
exportMode = getRightToIt
end
if exportMode == false then return false end
local pe, exportName, writeIndent, result
if exportMode == 4 then
local exNm = fileSave()
if exNm then
plc.changedImage = false
return exNm
else
return nil
end
else
checkBadDots()
if _fileName then
exportName, writeIndent = _fileName, #_fileName
else
exportName, writeIndent = bottomPrompt("Export to: /")
end
nfpoutput = ""
if fs.combine("",exportName) == "" then
barmsg = "Export cancelled."
return
end
if fs.isReadOnly(exportName) then
barmsg = "That's read-only."
return
end
if fs.exists(exportName) and (not _fileName) then
local plea = (plc.progname == fs.combine("",exportName)) and "Overwrite ORIGINAL file!?" or "Overwrite?"
result, _wIn = bottomPrompt(plea.." (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl})
writeIndent = writeIndent + _wIn
if result ~= "y" then return end
end
local output
pe = deepCopy(paintEncoded)
if paint.doGray then
pe = convertToGrayscale(pe)
end
local doSerializeBLT = false
end
if exportMode == 1 then
output = exportToPaint(pe[frame])
if plc.askToSerialize then
result, _wIn = bottomPrompt("Save as serialized? (Y/N)",_,"yn",{})
writeIndent = writeIndent + _wIn
else result, _wIn = "n", 0 end
if result == "y" then
output = textutils.serialize(NFPserializeImage(output)):gsub(" ",""):gsub("\n",""):gsub(",}","}")
end
elseif exportMode == 2 then
output = exportToNFT(pe[frame])
elseif exportMode == 3 then
local doAllFrames, _wIn = bottomPrompt("Save all frames, or current? (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl},writeIndent)
writeIndent = writeIndent + _wIn
if plc.askToSerialize then
doSerializeBLT = bottomPrompt("Save as serialized? (Y/N)",_,"yn",{},writeIndent) == "y"
end
output = textutils.serialise(exportToBLT(pe,exportName,doAllFrames == "y",doSerializeBLT))
elseif exportMode == 5 then
getGIF()
GIF.saveGIF(exportToGIF(pe),exportName)
elseif exportMode == 6 then
exportToUCG(exportName,pe[frame])
end
if ((exportMode ~= 3) and (exportMode ~= 4) and (exportMode ~= 5) and (exportMode ~= 6)) or doSerializeBLT then
local file = fs.open(exportName,"w")
file.write(output)
file.close()
end
return exportName
end
local editClear = function(ignorePrompt)
local outcum = ignorePrompt and "y" or bottomPrompt("Clear the frame? (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl})
if outcum == "y" then
paintEncoded[frame] = {}
saveToUndoBuffer()
barmsg = "Cleared frame "..frame.."."
end
end
local editDelFrame = function()
local outcum = bottomPrompt("Thou art sure? (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl})
if outcum == "y" then
if #paintEncoded == 1 then
return editClear(true)
end
table.remove(paintEncoded,frame)
barmsg = "Deleted frame "..frame.."."
if paintEncoded[frame-1] then
frame = frame - 1
else
frame = frame + 1
end
if #paintEncoded < frame then
repeat
frame = frame - 1
until #paintEncoded >= frame
end
saveToUndoBuffer()
end
end
local editCrop = function()
local outcum = bottomPrompt("Crop all but visible? (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl})
if outcum == "y" then
local ppos = 1
local deletedAmnt = 0
for a = #paintEncoded[frame], 1, -1 do
local x, y = paintEncoded[frame][a].x, paintEncoded[frame][a].y
if (x <= paint.scrollX) or (x > paint.scrollX + scr_x) or (y <= paint.scrollY) or (y > paint.scrollY + scr_y) then
table.remove(paintEncoded[frame],a)
deletedAmnt = deletedAmnt + 1
else
ppos = ppos + 1
end
if ppos > #paintEncoded[frame] then break end
end
saveToUndoBuffer()
barmsg = "Cropped frame."
end
end
local editBoxCharSelector = function()
paint.c = boxCharSelector()
end
local editSpecialCharSelector = function()
paint.c = specialCharSelector()
end
local windowSetScrSize = function()
local x,y
x = bottomPrompt("Scr.X OR monitor name:",{},nil,{keys.leftCtrl,keys.rightCtrl})
if x == "" then
return
elseif x == "pocket" then
screenEdges = {26,20}
elseif x == "turtle" then
screenEdges = {39,13}
elseif x == "computer" then
screenEdges = {51,19}
elseif tonumber(x) then
if tonumber(x) <= 0 then
barmsg = "Screen X must be greater than 0."
return
end
screenEdges[1] = math.abs(tonumber(x))
y = bottomPrompt("Scr.Y:",{},nil,{keys.leftCtrl,keys.rightCtrl})
if tonumber(y) then
if tonumber(y) <= 0 then
barmsg = "Screen Y must be greater than 0."
return
end
screenEdges[2] = math.abs(tonumber(y))
end
barmsg = "Screen size changed."
else
local mon = peripheral.wrap(x)
if not mon then
barmsg = "No such monitor."
return
else
if peripheral.getType(x) ~= "monitor" then
barmsg = "That's not a monitor."
return
else
screenEdges[1], screenEdges[2] = mon.getSize()
barmsg = "Screen size changed."
return
end
end
end
end
local aboutPAIN = function()
local helpText = [[
    
      
    
     
     
Advanced Paint Program
by LDDestroier
or EldidiStroyrr
if you please!
PAIN is a multi-frame paint program with the intention of becoming a stable, well-used, and mondo-useful CC drawing utility.
The main focus during development is to add many functions that you might see in real programs like MSPAINT, such as lines or a proper fill tool, as well as to export/import to and from as many image formats as possible.
My ultimate goal is to have PAIN be the default paint program for most every operating system on the forums (for what it's worth). 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.
I hope my PAIN brings you joy.
]]
guiHelp(helpText)
end
local aboutFileFormats = function()
local helpText = [[
Here's info on the file formats.
"NFP":
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.
Cannot save multiple frames.
"NFT":
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.
Cannot save multiple frames.
"BLT":
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.
BLT can save multiple frames!
"PAIN Native":
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.
Obviously, this can save multiple frames.
"GIF":
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.
Being GIF, this saves multiple frames!
"UCG":
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.
Cannot save multiple frames.
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.
]]
guiHelp(helpText)
end
local menuPoses = {}
local menuFunctions = {
[1] = function() --File
while true do
local output, longestLen = makeSubMenu(1,cleary-1,{
"Save",
"Save As",
"Export",
"Open",
((peripheral.find("printer")) and "Print" or nil)
})
if output == 1 then -- Save
local _fname = fileExport(_,plc.defaultSaveFormat,plc.fileName)
if _fname then
barmsg = "Saved as '".._fname.."'"
lastPaintEncoded = deepCopy(paintEncoded)
plc.changedImage = false
end
break
elseif output == 2 then -- Save As
local oldfilename = plc.fileName
plc.fileName = nil
local res = fileExport(_,plc.defaultSaveFormat)
if not res then
plc.fileName = oldfilename
else
barmsg = "Saved as '"..plc.fileName.."'"
end
break
elseif output == 3 then --Export
local res = fileExport(longestLen+1)
if res then
barmsg = "Exported as '"..res.."'"
end
break
elseif output == 4 then -- Open
renderBottomBar("Pick an image file.")
local newPath = lddfm.makeMenu(2, 2, scr_x-1, scr_y-2, fs.getDir(plc.fileName or plc.progname), false, false, false, true, false, nil, true)
if newPath then
local pen, form = openNewFile(newPath, painconfig.readNonImageAsNFP)
if not pen then
barmsg = form
else
plc.fileName = newPath
paintEncoded, lastPaintEncoded = pen, deepCopy(pen)
plc.defaultSaveFormat = form
plc.undoPos = 1
plc.undoBuffer = {deepCopy(paintEncoded)}
barmsg = "Opened '" .. fs.getName(newPath) .. "'"
paint.scrollX, paint.scrollY, paint.doGray = 1, 1, false
end
end
break
elseif output == 5 then -- Print
filePrint()
break
elseif output == false then
return "nobreak"
end
reRenderPAIN(true)
end
end,
[2] = function() --Edit
local output = makeSubMenu(6,cleary-1,{
"Delete Frame",
"Clear Frame",
"Crop Frame",
"Choose Box Character",
"Choose Special Character",
"BLittle Shrink",
"BLittle Grow",
"Copy Region",
"Cut Region",
"Paste Region"
})
if output == 1 then
editDelFrame()
elseif output == 2 then
editClear()
elseif output == 3 then
editCrop()
elseif output == 4 then
editBoxCharSelector()
elseif output == 5 then
editSpecialCharSelector()
elseif output == 6 then
local res = bottomPrompt("You sure? It's lossy! (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl})
if res == "y" then
getBlittle()
local bltPE = blittle.shrink(NFPserializeImage(exportToPaint(paintEncoded[frame])))
_G.SHRINKOUT = bltPE
paintEncoded[frame] = {}
for y = 1, bltPE.height do
for x = 1, bltPE.width do
paintEncoded[frame][#paintEncoded[frame]+1] = {
c = bltPE[1][y]:sub(x,x),
t = BTC(bltPE[2][y]:sub(x,x),true),
b = BTC(bltPE[3][y]:sub(x,x),true),
x = x,
y = y,
}
end
end
saveToUndoBuffer()
barmsg = "Shrunk image."
end
elseif output == 7 then
paintEncoded[frame] = blockEnlargeFrame(frame)
elseif output == 8 then
editFuncs.copy()
elseif output == 9 then
editFuncs.cut()
elseif output == 10 then
editFuncs.paste()
elseif output == false then
return "nobreak"
end
end,
[3] = function() --Window
local output = makeSubMenu(11,cleary-1,{
"Set Screen Size",
"Set Scroll XY",
"Set Grid Colors"
})
if output == 1 then
windowSetScrSize()
elseif output == 2 then
gotoCoords()
elseif output == 3 then
rendback.b = paint.b
rendback.t = paint.t
elseif output == false then
return "nobreak"
end
end,
[4] = function() --Set
local output = makeSubMenu(17,cleary-1,{
(painconfig.readNonImageAsNFP and "(T)" or "(F)") .. " Load Non-images",
(painconfig.useFlattenGIF and "(T)" or "(F)") .. " Flatten GIFs",
(painconfig.gridBleedThrough and "(T)" or "(F)") .. " Always Render Grid",
(painconfig.doFillDiagonal and "(T)" or "(F)") .. " Fill Diagonally",
(painconfig.doFillAnimation and "(T)" or "(F)") .. " Do Fill Animation",
(painconfig.useSetVisible and "(T)" or "(F)") .. " Use setVisible()",
"(" .. painconfig.undoBufferSize .. ") Set Undo Buffer Size",
})
if output == 1 then
painconfig.readNonImageAsNFP = not painconfig.readNonImageAsNFP
elseif output == 2 then
painconfig.useFlattenGIF = not painconfig.useFlattenGIF
elseif output == 3 then
painconfig.gridBleedThrough = not painconfig.gridBleedThrough
elseif output == 4 then
painconfig.doFillDiagonal = not painconfig.doFillDiagonal
elseif output == 5 then
painconfig.doFillAnimation = not painconfig.doFillAnimation
elseif output == 6 then
painconfig.useSetVisible = not painconfig.useSetVisible
elseif output == 7 then
local newUndoBufferSize = bottomPrompt("New undo buffer size: ")
if tonumber(newUndoBufferSize) then
painconfig.undoBufferSize = math.abs(tonumber(newUndoBufferSize))
plc.undoBuffer = {deepCopy(paintEncoded)}
plc.undoPos = 1
else
return
end
end
useConfig("save")
end,
[5] = function() --About
local output = makeSubMenu(17,cleary-1,{
"PAIN",
"File Formats",
"Help!"
})
if output == 1 then
aboutPAIN()
elseif output == 2 then
aboutFileFormats()
elseif output == 3 then
guiHelp()
end
end,
[6] = function() --Exit
if plc.changedImage then
local outcum = bottomPrompt("Abandon unsaved work? (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl})
sleep(0.05)
if outcum == "y" then
return "exit"
else
return nil
end
else
return "exit"
end
end,
}
local cursor = 1
local redrawmenu = true
local initial = os.time()
local clickdelay = 0.003
local redrawTheMenu = function()
for a = cleary,scr_y do
term.setCursorPos(1,a)
term.setBackgroundColor(colors.lightGray)
term.clearLine()
end
term.setCursorPos(2,cleary)
for a = 1, #menuOptions do
if a == cursor then
term.setTextColor(colors.black)
term.setBackgroundColor(colors.white)
else
term.setTextColor(colors.black)
term.setBackgroundColor(colors.lightGray)
end
menuPoses[a] = {term.getCursorPos()}
write(menuOptions[a])
term.setBackgroundColor(colors.lightGray)
if a ~= #menuOptions then
write(" ")
end
end
redrawmenu = false
end
while true do
if redrawmenu then
redrawTheMenu()
redrawmenu = false
end
local event,key,x,y = getEvents("key","char","mouse_click","mouse_up","mouse_drag")
if event == "key" then
if key == keys.left then
redrawmenu = true
cursor = cursor - 1
elseif key == keys.right then
redrawmenu = true
cursor = cursor + 1
elseif key == keys.enter then
redrawmenu = true
local res = menuFunctions[cursor]()
resetInputState()
if res == "exit" then
doRender = true
return "exit"
elseif res == "nobreak" then
reRenderPAIN(true)
else
doRender = true
return
end
elseif key == keys.leftCtrl or key == keys.rightCtrl then
doRender = true
return
end
elseif event == "char" then
for a = 1, #menuOptions do
if key:lower() == menuOptions[a]:sub(1,1):lower() and a ~= cursor then
cursor = a
redrawmenu = true
break
end
end
elseif event == "mouse_click" or event == "mouse_up" then
if y < cleary then
resetInputState()
doRender = true
return
elseif key == 1 and initial+clickdelay < os.time() then --key? more like button
for a = 1, #menuPoses do
if y == menuPoses[a][2] then
if x >= menuPoses[a][1] and x <= menuPoses[a][1]+#menuOptions[a] then
cursor = a
redrawTheMenu()
local res = menuFunctions[a]()
os.queueEvent("queue")
os.pullEvent("queue")
resetInputState()
doRender = true
if res == "exit" then
return "exit"
else
return
end
end
end
end
end
end
if (initial+clickdelay < os.time()) and string.find(event,"mouse") then
if key == 1 then --key? key? what key? all I see is button!
for a = 1, #menuPoses do
if y == menuPoses[a][2] then
if x >= menuPoses[a][1] and x <= menuPoses[a][1]+#menuOptions[a] then
cursor = a
redrawmenu = true
break
end
end
end
end
end
if cursor < 1 then
cursor = #menuOptions
elseif cursor > #menuOptions then
cursor = 1
end
end
end
local lastMX,lastMY,isDragging
local doNonEventDrivenMovement = function() --what a STUPID function name, dude
local didMove
while true do
didMove = false
if (not keysDown[keys.leftShift]) and (not isDragging) and (not keysDown[keys.tab]) then
if keysDown[keys.right] then
paint.scrollX = paint.scrollX + 1
didMove = true
elseif keysDown[keys.left] then
paint.scrollX = paint.scrollX - 1
didMove = true
end
if keysDown[keys.down] then
paint.scrollY = paint.scrollY + 1
didMove = true
elseif keysDown[keys.up] then
paint.scrollY = paint.scrollY - 1
didMove = true
end
if didMove then
if lastMX and lastMY then
if miceDown[1] then
os.queueEvent("mouse_click",1,lastMX,lastMY)
end
if miceDown[2] then
os.queueEvent("mouse_click",2,lastMX,lastMY)
end
end
doRender = true
end
end
sleep(0.05)
end
end
local linePoses = {}
local dragPoses = {}
local listAllMonitors = function()
term.setBackgroundColor(colors.gray)
term.setTextColor(colors.white)
local periphs = peripheral.getNames()
local mons = {}
for a = 1, #periphs do
if peripheral.getType(periphs[a]) == "monitor" then
mons[#mons+1] = periphs[a]
end
end
if #mons == 0 then
mons[1] = "No monitors found."
end
term.setCursorPos(3,1)
term.clearLine()
term.setTextColor(colors.yellow)
term.write("All monitors:")
term.setTextColor(colors.white)
for y = 1, #mons do
term.setCursorPos(2,y+1)
term.clearLine()
term.write(mons[y])
end
sleep(0.05)
getEvents("char","mouse_click")
doRender = true
end
local getInput = function() --gotta catch them all
local drawEveryEvent = false
local doot = function()
local button, x, y, oldmx, oldmy, origx, origy
local isDragging = false
local proceed = false
local evt, oldx, oldy = {}
local button, points, key, dir
renderBar(barmsg)
while true do
evt = {getEvents("mouse_scroll","mouse_click", "mouse_drag","mouse_up","key","key_up",true)}
--doRender = false
oldx, oldy = paint.scrollX,paint.scrollY
if (evt[1] == "mouse_scroll") and (not viewing) then
dir = evt[2]
if dir == 1 then
if keysDown[keys.leftShift] or keysDown[keys.rightShift] then
paint.t = paint.t * 2
if paint.t > 32768 then
paint.t = 32768
end
else
paint.b = paint.b * 2
if paint.b > 32768 then
paint.b = 32768
end
end
else
if keysDown[keys.leftShift] or keysDown[keys.rightShift] then
paint.t = math.ceil(paint.t / 2)
if paint.t < 1 then
paint.t = 1
end
else
paint.b = math.ceil(paint.b / 2)
if paint.b < 1 then
paint.b = 1
end
end
end
renderBar(barmsg)
elseif ((evt[1] == "mouse_click") or (evt[1] == "mouse_drag")) and (not viewing) then
if evt[1] == "mouse_click" then
origx, origy = evt[3], evt[4]
end
oldmx,oldmy = x or evt[3], y or evt[4]
lastMX,lastMY = evt[3],evt[4]
button,x,y = evt[2],evt[3],evt[4]
if plc.renderBlittle then
x = 2*x
y = 3*y
lastMX = 2*lastMX
lastMY = 3*lastMY
end
linePoses = {{x=oldmx,y=oldmy},{x=x,y=y}}
miceDown[button] = true
if y <= scr_y-(plc.renderBlittle and 0 or doRenderBar) then
if (button == 3) then
renderBottomBar("Type text onto canvas:")
putDownText(x,y)
miceDown = {}
keysDown = {}
doRender = true
elseif button == 1 then
if keysDown[keys.leftShift] and evt[1] == "mouse_click" then
isDragging = true
end
if isDragging then
if evt[1] == "mouse_click" or dontDragThisTime then
dragPoses[1] = {x=x,y=y}
end
dragPoses[2] = {x=x,y=y}
points = getDotsInLine(dragPoses[1].x,dragPoses[1].y,dragPoses[2].x,dragPoses[2].y)
renderAllPAIN()
for a = 1, #points do
term.setCursorPos(points[a].x, points[a].y)
term.blit(paint.c, CTB(paint.t), CTB(paint.b))
end
elseif (not dontDragThisTime) then
if evt[1] == "mouse_drag" then
points = getDotsInLine(linePoses[1].x,linePoses[1].y,linePoses[2].x,linePoses[2].y)
for a = 1, #points do
putDotDown({x=points[a].x, y=points[a].y})
end
else
putDotDown({x=x, y=y})
end
plc.changedImage = true
doRender = true
end
dontDragThisTime = false
elseif button == 2 and y <= scr_y-(plc.renderBlittle and 0 or doRenderBar) then
deleteDot(x+paint.scrollX,y+paint.scrollY)
plc.changedImage = true
doRender = true
end
elseif origy >= scr_y-(plc.renderBlittle and 0 or doRenderBar) then
miceDown = {}
keysDown = {}
isDragging = false
local res = displayMenu()
if res == "exit" then break end
doRender = true
end
elseif (evt[1] == "mouse_up") and (not viewing) and (not plc.isCurrentlyFilling) then
origx,origy = 0,0
button = evt[2]
miceDown[button] = false
oldmx,oldmy = nil,nil
lastMX, lastMY = nil,nil
if isDragging then
points = getDotsInLine(dragPoses[1].x,dragPoses[1].y,dragPoses[2].x,dragPoses[2].y)
for a = 1, #points do
putDotDown({x=points[a].x, y=points[a].y})
end
plc.changedImage = true
doRender = true
end
saveToUndoBuffer()
isDragging = false
elseif evt[1] == "key" then
key = evt[2]
if (isDragging or not keysDown[keys.leftShift]) and (keysDown[keys.tab]) then
if key == keys.right and (not keysDown[keys.right]) then
paint.scrollX = paint.scrollX + 1
doRender = true
elseif key == keys.left and (not keysDown[keys.left]) then
paint.scrollX = paint.scrollX - 1
doRender = true
end
if key == keys.down and (not keysDown[keys.down]) then
paint.scrollY = paint.scrollY + 1
doRender = true
elseif key == keys.up and (not keysDown[keys.up]) then
paint.scrollY = paint.scrollY - 1
doRender = true
end
end
keysDown[key] = true
if key == keys.space then
if keysDown[keys.leftShift] then
plc.evenDrawGrid = not plc.evenDrawGrid
else
doRenderBar = math.abs(doRenderBar-1)
end
doRender = true
end
if key == keys.b then
local blTerm, oldTerm = getBlittle()
plc.renderBlittle = not plc.renderBlittle
isDragging = false
term.setBackgroundColor(rendback.b)
term.clear()
if plc.renderBlittle then
term.redirect(blTerm)
blTerm.setVisible(true)
else
term.redirect(oldTerm)
blTerm.setVisible(false)
end
doRender = true
scr_x, scr_y = term.current().getSize()
end
if keysDown[keys.leftAlt] then
if (not plc.renderBlittle) then
if (key == keys.c) then
editFuncs.copy()
elseif (key == keys.x) then
editFuncs.cut()
elseif (key == keys.v) then
editFuncs.paste()
end
end
else
if (key == keys.c) and (not plc.renderBlittle) then
gotoCoords()
resetInputState()
doRender = true
end
end
if (keysDown[keys.leftShift]) and (not isDragging) then
if key == keys.left then
paintEncoded[frame] = movePaintEncoded(paintEncoded[frame],-1,0)
saveToUndoBuffer()
doRender = true
plc.changedImage = true
elseif key == keys.right then
paintEncoded[frame] = movePaintEncoded(paintEncoded[frame],1,0)
saveToUndoBuffer()
doRender = true
plc.changedImage = true
elseif key == keys.up then
paintEncoded[frame] = movePaintEncoded(paintEncoded[frame],0,-1)
saveToUndoBuffer()
doRender = true
plc.changedImage = true
elseif key == keys.down then
paintEncoded[frame] = movePaintEncoded(paintEncoded[frame],0,1)
saveToUndoBuffer()
doRender = true
plc.changedImage = true
end
end
if keysDown[keys.leftAlt] then
if #paintEncoded > 1 then
if key == keys.equals and paintEncoded[frame+1] then --basically plus
local first = deepCopy(paintEncoded[frame])
local next = deepCopy(paintEncoded[frame+1])
paintEncoded[frame] = next
paintEncoded[frame+1] = first
frame = frame + 1
barmsg = "Swapped prev frame."
doRender = true
plc.changedImage = true
saveToUndoBuffer()
end
if key == keys.minus and paintEncoded[frame-1] then
local first = deepCopy(paintEncoded[frame])
local next = deepCopy(paintEncoded[frame-1])
paintEncoded[frame] = next
paintEncoded[frame-1] = first
frame = frame - 1
barmsg = "Swapped next frame."
doRender = true
plc.changedImage = true
saveToUndoBuffer()
end
end
elseif keysDown[keys.leftShift] then
if #paintEncoded > 1 then
if key == keys.equals and paintEncoded[frame+1] then --basically plus
for a = 1, #paintEncoded[frame] do
paintEncoded[frame+1][#paintEncoded[frame+1] + 1] = paintEncoded[frame][a]
end
table.remove(paintEncoded, frame)
paintEncoded = clearAllRedundant(paintEncoded)
barmsg = "Merged next frame."
doRender = true
plc.changedImage = true
saveToUndoBuffer()
end
if key == keys.minus and paintEncoded[frame-1] then
for a = 1, #paintEncoded[frame] do
paintEncoded[frame-1][#paintEncoded[frame-1] + 1] = paintEncoded[frame][a]
end
table.remove(paintEncoded, frame)
frame = frame - 1
paintEncoded = clearAllRedundant(paintEncoded)
barmsg = "Merged previous frame."
doRender = true
plc.changedImage = true
saveToUndoBuffer()
end
end
else
if key == keys.equals then --basically 'plus'
if plc.renderBlittle then
frame = frame + 1
if frame > #paintEncoded then frame = 1 end
else
if not paintEncoded[frame+1] then
paintEncoded[frame+1] = {}
local sheet = paintEncoded[frame]
if keysDown[keys.rightShift] then
paintEncoded[frame+1] = deepCopy(sheet)
end
end
frame = frame + 1
end
saveToUndoBuffer()
doRender = true
plc.changedImage = true
elseif key == keys.minus then
if plc.renderBlittle then
frame = frame - 1
if frame < 1 then frame = #paintEncoded end
else
if frame > 1 then
frame = frame - 1
end
end
saveToUndoBuffer()
doRender = true
plc.changedImage = true
end
end
if not plc.renderBlittle then
if key == keys.m then
local incum = bottomPrompt("Set meta: ",metaHistory)
paint.m = incum:gsub(" ","") ~= "" and incum or paint.m
if paint.m ~= metaHistory[#metaHistory] then
metaHistory[#metaHistory+1] = paint.m
end
doRender = true
isDragging = false
end
if key == keys.f7 then
plc.bepimode = not plc.bepimode
doRender = true
end
if key == keys.t then
renderBottomBar("Click to place text.")
local mevt
repeat
mevt = {os.pullEvent()}
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-(plc.renderBlittle and 0 or doRenderBar))
if not (mevt[1] == "key" and mevt[2] == keys.x) then
local x,y = mevt[3],mevt[4]
if plc.renderBlittle then
x = 2*x
y = 3*y
end
renderBottomBar("Type text onto canvas:")
putDownText(x,y)
miceDown = {}
keysDown = {}
end
doRender = true
plc.changedImage = true
isDragging = false
end
if key == keys.f and not (keysDown[keys.leftShift] or keysDown[keys.rightShift]) and (not plc.isCurrentlyFilling) then
renderBottomBar("Click to fill area.")
local mevt
repeat
mevt = {os.pullEvent()}
until (mevt[1] == "key" and mevt[2] == keys.x) or (mevt[1] == "mouse_click" and mevt[2] <= 2 and (mevt[4] or scr_y) <= scr_y-(renderBlittle and 0 or doRenderBar))
if not (mevt[1] == "key" and mevt[2] == keys.x) then
local x,y = mevt[3],mevt[4]
if plc.renderBlittle then
x = 2*x
y = 3*y
end
miceDown = {}
keysDown = {}
os.queueEvent("filltool_async", frame, x, y, paint, mevt[2] == 2)
end
doRender = true
plc.changedImage = true
isDragging = false
end
if key == keys.p then
renderBottomBar("Pick color with cursor:")
paintEncoded = clearAllRedundant(paintEncoded)
local mevt
local keepPicking = true
while keepPicking do
mevt = {os.pullEvent()}
if mevt[1] == "mouse_click" then
keepPicking = false
resetInputState()
if mevt[4] < scr_y then
local x, y = mevt[3]+paint.scrollX, mevt[4]+paint.scrollY
if plc.renderBlittle then
x = 2*x
y = 3*y
end
local p
for a = 1, #paintEncoded[frame] do
p = paintEncoded[frame][a]
if (p.x == x) and (p.y == y) then
paint.t = p.t or paint.t
paint.b = p.b or paint.b
paint.c = p.c or paint.c
paint.m = p.m or paint.m
break
end
end
end
elseif mevt[1] == "key" then
if mevt[2] == keys.x or evt[2] == keys.q then
keepPicking = false
end
end
end
doRender = true
isDragging = false
end
if (key == keys.leftCtrl or key == keys.rightCtrl) then
keysDown = {[207] = keysDown[207]}
isDragging = false
local res = displayMenu()
paintEncoded = clearAllRedundant(paintEncoded)
if res == "exit" then break end
doRender = true
end
end
if (key == keys.f and keysDown[keys.leftShift]) then
local deredots = {}
plc.changedImage = true
for a = 1, #paintEncoded[frame] do
local dot = paintEncoded[frame][a]
if dot.x-paint.scrollX > 0 and dot.x-paint.scrollX <= scr_x then
if dot.y-paint.scrollY > 0 and dot.y-paint.scrollY <= scr_y then
deredots[#deredots+1] = {dot.x-paint.scrollX, dot.y-paint.scrollY}
end
end
end
for y = 1, scr_y do
for x = 1, scr_x do
local good = true
for a = 1, #deredots do
if (deredots[a][1] == x) and (deredots[a][2] == y) then
good = bad
break
end
end
if good then
putDotDown({x=x, y=y})
end
end
end
saveToUndoBuffer()
doRender = true
end
if key == keys.g then
paint.doGray = not paint.doGray
plc.changedImage = true
saveToUndoBuffer()
doRender = true
end
if key == keys.a then
paint.scrollX = 0
paint.scrollY = 0
doRender = true
end
if key == keys.n then
if keysDown[keys.leftShift] then
paint.c = specialCharSelector()
else
paint.c = boxCharSelector()
end
resetInputState()
doRender = true
end
if key == keys.f1 then
guiHelp()
resetInputState()
isDragging = false
end
if key == keys.f3 then
listAllMonitors()
resetInputState()
isDragging = false
end
if key == keys.leftBracket then
os.queueEvent("mouse_scroll",2,1,1)
elseif key == keys.rightBracket then
os.queueEvent("mouse_scroll",1,1,1)
end
if key == keys.z then
if keysDown[keys.leftAlt] and plc.undoPos < #plc.undoBuffer then
doRedo()
barmsg = "Redood."
doRender = true
elseif plc.undoPos > 1 then
doUndo()
barmsg = "Undood."
doRender = true
end
end
elseif evt[1] == "key_up" then
local key = evt[2]
keysDown[key] = false
end
if (oldx~=paint.scrollX) or (oldy~=paint.scrollY) then
doRender = true
end
if drawEveryEvent and doRender then
renderAllPAIN()
doRender = false
end
end
end
if drawEveryEvent then
doot()
else
parallel.waitForAny(doot, function()
while true do
sleep(0.05)
if doRender then
renderAllPAIN()
doRender = false
end
end
end)
end
end
runPainEditor = function(...) --needs to be cleaned up
local tArg = table.pack(...)
if not (tArg[1] == "-n" or (not tArg[1])) then
plc.fileName = shell.resolve(tostring(tArg[1]))
end
if not plc.fileName then
paintEncoded = {{}}
elseif not fs.exists(plc.fileName) then
local ex = plc.fileName:sub(-4):lower()
if ex == ".nfp" then
plc.defaultSaveFormat = 1
elseif ex == ".nft" then
plc.defaultSaveFormat = 2
elseif ex == ".blt" then
plc.defaultSaveFormat = 3
elseif ex == ".gif" then
plc.defaultSaveFormat = 5
elseif ex == ".ucg" then
plc.defaultSaveFormat = 6
else
plc.defaultSaveFormat = 4
end
paintEncoded = {{}}
elseif fs.isDir(plc.fileName) then
if math.random(1,32) == 1 then
write("Oh") sleep(0.2)
write(" My") sleep(0.2)
print(" God") sleep(0.3)
write("That is a") sleep(0.1) term.setTextColor(colors.red)
write(" FLIPPING") sleep(0.4)
print(" FOLDER.") sleep(0.2) term.setTextColor(colors.white)
print("You crazy person.") sleep(0.2)
else
print("That's a folder.")
end
return
else
paintEncoded, plc.defaultSaveFormat = openNewFile(plc.fileName, readNonImageAsNFP)
if not paintEncoded then
return print(plc.defaultSaveFormat)
end
end
local asyncFillTool = function()
local event, frameNo, x, y, dot
plc.isCurrentlyFilling = false
while true do
event, frameNo, x, y, dot, isDeleting = os.pullEvent("filltool_async")
plc.isCurrentlyFilling = true
renderBottomBar("Filling area...")
fillTool(frameNo, x, y, dot, isDeleting)
saveToUndoBuffer()
plc.isCurrentlyFilling = false
reRenderPAIN(doRenderBar == 0)
end
end
if not paintEncoded[frame] then paintEncoded = {paintEncoded} end
if plc.pMode == 1 then
doRenderBar = 0
renderPAIN(paintEncoded[tonumber(tArg[5]) or 1],-(tonumber(tArg[3]) or 0),-(tonumber(tArg[4]) or 0)) -- 'pain filename view X Y frame'
sleep(0.05)
return
else
renderPAIN(paintEncoded[frame],paint.scrollX,paint.scrollY,true)
end
lastPaintEncoded = deepCopy(paintEncoded)
plc.undoBuffer = {deepCopy(paintEncoded)}
parallel.waitForAny(getInput, doNonEventDrivenMovement, asyncFillTool)
term.setCursorPos(1,scr_y)
term.setBackgroundColor(colors.black)
term.clearLine()
end
if not shell then error("shell API is required, sorry") end
runPainEditor(table.unpack(plc.tArg))