ldd-CC/pain.lua

2866 lines
75 KiB
Lua
Raw Normal View History

2018-10-21 20:31:50 +00:00
--[[
PAIN image editor for ComputerCraft
Get it with
wget https://raw.githubusercontent.com/LDDestroier/CC/beta/pain.lua
2018-10-21 20:48:38 +00:00
std ld pain pain
2018-10-21 20:31:50 +00:00
This is a beta release. You fool!
To do:
+ a god damned fill function
2018-10-21 20:31:50 +00:00
--]]
local askToSerialize = false
local defaultSaveFormat = 4 -- will change if importing image, or making new file with extension in name
--[[
defaultSaveFormat possible parameters:
1. NFP (paint)
2. NFT (npaintpro)
3. BLT (blittle)
4. Native PAIN
5. GIF
6. UCG
--]]
local readNonImageAsNFP = true
local useFlattenGIF = true
local undoBufferSize = 8
local displayHelp = function()
local progname = fs.getName(shell.getRunningProgram())
print(progname)
print(progname.." <filename>")
print(progname.." [-h/--help]")
print("Press F1 in program for more.")
end
local tsv = term.current().setVisible
local undoBuffer
local undoPos = 1
local pMode = 0
local scr_x, scr_y = term.getSize()
screenEdges = {
scr_x,
scr_y,
}
local tArg = {...}
if (tArg[1] == "--help" or tArg[1] == "-h") and shell then
return displayHelp()
end
if tArg[2] == "view" then
pMode = 1
elseif (tArg[2] == "moo") and (not fs.exists("moo")) then
return print("This PAIN does not have Super Cow Powers.")
end
local fileName
if (not term.isColor()) and (pMode ~= 1) then
error("PAIN only works with Advanced Computers at the moment.")
end
local barmsg = "Press F1 for help."
local tse = textutils.serialise
local tun = textutils.unserialise
local paintEncoded
local lastPaintEncoded
local frame = 1
local doRender = false
local metaHistory = {}
local bepimode = false -- this is a family-friendly program! now stand still while I murder you
local evenDrawGrid = true -- will you evenDraw(the)Grid ?
local renderBlittle = false -- whether or not to render all in blittle
local firstTerm, blittleTerm = term.current()
local firstBG = term.getBackgroundColor()
local firstTX = term.getTextColor()
local changedImage = false
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
2018-10-21 20:31:50 +00:00
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 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' Help Page
Programmed by LDDestroier/EldidiStroyrr
(use UP/DOWN or scrollwheel, exit with Q)
If you want to use PAIN to its full capacity, then READ EVERYTHING HERE! Its not TOO long, and it's completely worth it!
Syntax:
>pain <filename> [view]
>pain [-n]
>pain [-h/--help]
[view]: disable all writing capability to view a file
"-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:
+shift = drag and let go to make a line
-alone = place pixel
right click: delete pixel
middle click OR "t": place text down with current colors, cancel with X
"z":
+left alt = redo
-alone = undo
"p": pick colors from position onscreen, cancel with X
2018-10-22 00:12:25 +00:00
"n":
+left shift = change character to that of a special character
-alone = change box character for drawing
(cancel with CTRL, N, or by clicking outside)
2018-10-21 20:31:50 +00:00
"[" or mouse scroll down:
+shift = change to previous text color
-alone = change to previous background color
"]" or mouse scroll up:
+shift = change to next text color
-alone = change to next background color
"F1":
-alone = access help screen
"F3:"
-alone = view all connected monitors
spacebar:
+shift = toggle grid
-alone = toggle bar visibility
arrow keys:
+shift = move entire picture
+tab = move one pixel at a time
-alone = looks around the canvas smoothly
"+" (or equals):
+left alt = swap the current frame with the next frame
-alone = change to next frame
"-":
+left alt = swap the current frame with the previous frame
-alone = change to previous frame
"a": set the coordinates to 0,0
"n": open block character selection
"b": toggle redirect to blittle, to preview in teletext characters
"c": input coordinates to scroll over to
"g": toggle grayscale mode. everything is in shades of gray. if you Save, it saves in grayscale.
"f":
+shift = fill all empty pixels with background color and selected box character
-alone = absolutely nothing (which is what you are about to become)
"m": set metadata for pixels (for game makers, otherwise safe to ignore)
2018-10-22 00:12:25 +00:00
Yonder Menu (accessible with CTRL):
2018-10-21 20:31:50 +00:00
-left click on a menu item to select it.
-if you click on the menubar, let go 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\39t use text or multiple frame.
2018-10-22 00:12:25 +00:00
"File > Save As"
2018-10-21 20:31:50 +00:00
Same as "File > Save", but you change the filename.
"File > Export"
Exports current frame to NFP, NFT, BLT, or the horribly inefficient PAIN format.
"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.
2018-10-22 00:12:25 +00:00
"Edit > Change Special Character"
Opens the special character selector, which lets you change the paint character to that of byte 0 to 255.
2018-10-21 20:31:50 +00:00
"Edit > BLittle Shrink"
Shrinks the current frame using the BLittle API. Very lossy, and unreversable without Undo.
"Window > Set Screen Size"
Sets the sizes of the screen border references displayed on the canvas.
"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"
Durr I dunno, I think it exits.
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 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)
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
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
2018-10-22 16:44:32 +00:00
local minX, minY = 0, 0
2018-10-21 20:31:50 +00:00
for a = 1, #dots do
2018-10-22 16:44:32 +00:00
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)
2018-10-21 20:31:50 +00:00
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
2018-10-22 16:44:32 +00:00
for y = minY, pheight do
for x = minX, pwidth do
2018-10-21 20:31:50 +00:00
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 fillTool = function(frame,cx,cy,dot) -- takes a frame, not the whole paintEncoded
local maxX, maxY = 0, 0
local minX, minY = 0, 0
2018-10-26 14:18:51 +00:00
local scx, scy = cx+paint.scrollX, cy+paint.scrollY
2018-10-21 20:31:50 +00:00
local output = {}
2018-10-26 14:18:51 +00:00
local initDot
for a = 1, #frame do
maxX = math.max(maxX, frame[a].x)
maxY = math.max(maxY, frame[a].y)
2018-10-26 14:18:51 +00:00
minX = math.min(minX, frame[a].x)
minY = math.min(minY, frame[a].y)
end
local doop = {}
for y = minY, maxY do
doop[y] = {}
for x = minX, maxX do
doop[y][x] = false
end
end
for a = 1, #frame do
doop[frame[a].y][frame[a].x] = frame[a]
if frame[a].x == scx and frame[a].y == scy then
initDot = frame[a]
end
2018-10-21 20:31:50 +00:00
end
local touched = {}
2018-10-26 14:18:51 +00:00
local check = {{scx, scy}}
2018-10-25 23:05:37 +00:00
local chkpos = function(x, y)
if (x < minX or x > maxX) or (y < minY or y > maxY) then
return false
else
2018-10-26 14:18:51 +00:00
if initDot then
if (doop[y][x].b ~= initDot.b) or (doop[y][x].t ~= initDot.t) or (doop[y][x].c ~= initDot.c) then
return false
end
elseif doop[y][x] then
return false
end
2018-10-25 23:05:37 +00:00
for a = 1, #touched do
if (touched[1] == x and touched[2] == y) then
return false
end
end
2018-10-26 14:18:51 +00:00
for a = 1, #check do
if (check[1] == x and check[2] == y) then
return false
end
end
2018-10-25 23:05:37 +00:00
return true
end
end
local a = 0
while true do
2018-10-25 23:05:37 +00:00
a = a + 1
2018-10-26 14:18:51 +00:00
chX, chY = check[a][1], check[a][2]
2018-10-25 23:05:37 +00:00
frame[#frame+1] = {
2018-10-26 14:18:51 +00:00
x = chX,
y = chY,
2018-10-25 23:05:37 +00:00
c = dot.c,
t = dot.t,
b = dot.b
}
touched[#touched+1] = {chX, chY}
if chkpos(chX+1, chY) then
check[#check+1] = {chX+1, chY}
end
if chkpos(chX-1, chY) then
check[#check+1] = {chX-1, chY}
end
if chkpos(chX, chY+1) then
check[#check+1] = {chX, chY+1}
end
if chkpos(chX, chY-1) then
check[#check+1] = {chX, chY-1}
end
if #touched == #check then
break
2018-10-21 20:31:50 +00:00
end
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 renderBlittle then return end
if tsv and (not dontSetVisible) then 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 tsv and (not dontSetVisible) then tsv(true) end
end
local getTablePaint = function(pe)
local output = {}
for a = 1, #pe do
if not output[pe[a].y] then output[pe[a].y] = {} end
output[pe[a].y][pe[a].x] = pe[a]
end
return output
end
local renderPainyThings = function(xscroll,yscroll,doGrid)
local yadjust = (renderBlittle and 0 or doRenderBar)
if 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"
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
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
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 getBlittle = function()
if not blittle then
if fs.exists("/.painapi/blittle") then
os.loadAPI("/.painapi/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("/.painapi/blittle","w")
file.write(geet)
file.close()
os.loadAPI("/.painapi/blittle")
--fs.delete("/.painapi/")
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("/.painapi/ucg") then
os.loadAPI("/.painapi/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("/.painapi/ucg","w")
file.write(geet)
file.close()
os.loadAPI("/.painapi/ucg")
end
end
end
end
local getBitmap = function()
if not bitmap then
if fs.exists("/.painapi/bitmap") then
os.loadAPI("/.painapi/bitmap")
return true
else
local geet = http.get("https://pastebin.com/raw/Y3JeZWzV")
if not geet then
return false
else
geet = geet.readAll()
local file = fs.open("/.painapi/bitmap","w")
file.write(geet)
file.close()
os.loadAPI("/.painapi/bitmap")
end
end
end
end
local getBBPack = function()
if not bbpack then
if fs.exists("/.painapi/bbpack") then
os.loadAPI("/.painapi/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("/.painapi/bbpack","w")
file.write(geet)
file.close()
os.loadAPI("/.painapi/bbpack")
end
end
end
end
local getGIF = function()
getBBPack()
if not GIF then
if fs.exists("/.painapi/GIF") then
os.loadAPI("/.painapi/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("/.painapi/GIF","w")
file.write(geet)
file.close()
os.loadAPI("/.painapi/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 importFromBitmap = function(fileName)
getBitmap()
local image = bitmap.ImageHelpers.parseFile(fileName).pixels
local output = {}
local closest, termSets = bitmap.Colors.findClosestColor, bitmap.Colors.termSets
for y = 1, #image do
local line = image[y]
for x = 1, #line do
output[#output+1] = {
x = x,
y = y,
t = colors.white,
c = " ",
b = closest(termSets, unpack(line[x])),
m = 0,
}
end
end
return output
end
local exportToBitmap = function(theInput)
getBitmap()
--er, I don't think this is possible at the moment
end
local importFromGIF = function(filename,verbose)
getGIF()
local output = {}
local image
local rawGif = GIF.loadGIF(filename)
if useFlattenGIF then
if verbose then
print("Flattening...")
end
rawGif = GIF.flattenGIF(rawGif)
sleep(0)
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) 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)
end
if 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)
if tsv then tsv(false) end
local beforeTX,beforeBG = term.getTextColor(), term.getBackgroundColor()
local cx,cy = term.getCursorPos()
local FUCK, SHIT = pcall(function()
if doPain then
if (not renderBlittle) then
if not dontRenderBar then
renderBar(barmsg,true)
end
renderPainyThings(xscroll,yscroll,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-(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.setTextColor( (paint.doGray and grayOut(d.t) or d.t) or rendback.t)
term.setBackgroundColor((paint.doGray and grayOut(d.b) or d.b) or rendback.b)
term.write(d.c or " ")
end
end
end)
term.setBackgroundColor(beforeBG or rendback.b)
term.setTextColor(beforeTX or rendback.t)
term.setCursorPos(cx,cy)
if tsv then tsv(true) end
if not FUCK then error(SHIT) end --GOD DAMN IT
end
renderPAINFS = function(filename,xscroll,yscroll,frameNo,doPain)
local tun, tse = textutils.unserialize, textutils.serialize
local file = fs.open(filename,"r")
local contents = file.readAll()
local amntFrames
file.close()
local tcontents = tun(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 undoPos < #undoBuffer then
for a = #undoBuffer, undoPos+1, -1 do
table.remove(undoBuffer,a)
end
end
if undoPos >= undoBufferSize then
for a = 2, #undoBuffer do
undoBuffer[a-1] = undoBuffer[a]
end
undoBuffer[#undoBuffer] = deepCopy(paintEncoded)
else
undoPos = undoPos + 1
undoBuffer[undoPos] = deepCopy(paintEncoded)
end
end
local doUndo = function()
undoPos = math.max(1,undoPos-1)
paintEncoded = deepCopy(undoBuffer[undoPos])
if not paintEncoded[frame] then
frame = #paintEncoded
end
end
local doRedo = function()
undoPos = math.min(#undoBuffer,undoPos+1)
paintEncoded = deepCopy(undoBuffer[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 pwidths = {}
local doot = {}
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
local bgcode, txcode = "\30", "\31"
local output = ""
local text, back
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
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()
local _reallyDoRenderBar = doRenderBar
doRenderBar = 1
renderPAIN(paintEncoded[frame],paint.scrollX,paint.scrollY,true,true)
doRenderBar = _reallyDoRenderBar
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()
2018-10-21 20:58:41 +00:00
term.setCursorPos(1,scr_y)
2018-10-21 20:31:50 +00:00
term.setBackgroundColor(colors.lightGray)
2018-10-21 20:58:41 +00:00
term.setTextColor(colors.black)
2018-10-21 20:31:50 +00:00
term.clearLine()
2018-10-21 20:58:41 +00:00
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")
2018-10-21 20:31:50 +00:00
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-2 then
if mx == 1 then
2018-10-21 20:58:41 +00:00
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
2018-10-21 20:31:50 +00:00
elseif mx == 2 then
2018-10-21 20:58:41 +00:00
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
2018-10-21 20:31:50 +00:00
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
2018-10-21 20:48:38 +00:00
local specialCharSelector = function()
local chars = {}
local buff = 0
2018-10-21 20:52:55 +00:00
for y = 1, 16 do
for x = 1, 16 do
2018-10-21 20:48:38 +00:00
chars[y] = chars[y] or {}
chars[y][x] = string.char(buff)
2018-10-21 20:52:55 +00:00
buff = buff + 1
2018-10-21 20:48:38 +00:00
end
end
2018-10-21 20:53:50 +00:00
local sy = scr_y - (#chars + 1)
2018-10-21 20:48:38 +00:00
local char = paint.c
local render = function()
for y = 1, #chars do
for x = 1, #chars do
2018-10-21 20:49:52 +00:00
term.setCursorPos(x,y+sy)
2018-10-21 20:48:38 +00:00
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()
2018-10-21 20:58:41 +00:00
term.setCursorPos(1,scr_y)
term.setBackgroundColor(colors.lightGray)
term.setTextColor(colors.black)
term.clearLine()
term.write("Press CTRL or 'N' when ready.")
2018-10-21 20:48:38 +00:00
while true do
evt, butt, x, y = os.pullEvent()
2018-10-21 21:00:10 +00:00
if (evt == "mouse_click" or evt == "mouse_drag") then
2018-10-21 20:48:38 +00:00
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
else
return char
end
else
return char
end
elseif evt == "key" then
2018-10-22 00:12:25 +00:00
if (butt == keys.n) or (butt == keys.leftCtrl) then
2018-10-21 20:48:38 +00:00
return char
end
end
end
end
2018-10-21 20:31:50 +00:00
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 displayMenu = function()
menuOptions = {"File","Edit","Window","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
doRender = true
if not 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
fileName = fnguess
end
end
saveFile(fileName,output)
term.setCursorPos(9,scr_y)
return 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)
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
doRender = true
if exportMode == false then return false end
local pe, exportName, writeIndent, result
if exportMode == 4 then
local exNm = fileSave()
if exNm then
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 = (shell.getRunningProgram() == 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 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 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 editDelFrame = function()
local outcum = bottomPrompt("Thou art sure? (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl})
doRender = true
if outcum == "y" then
if #paintEncoded == 1 then
barmsg = "Fat chance."
return
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 editClear = function()
local outcum = bottomPrompt("Clear the frame? (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl})
if outcum == "y" then
paintEncoded[frame] = {}
saveToUndoBuffer()
barmsg = "Cleared frame "..frame.."."
end
doRender = true
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
doRender = true
end
local editBoxCharSelector = function()
paint.c = boxCharSelector()
end
2018-10-21 20:48:38 +00:00
local editSpecialCharSelector = function()
paint.c = boxCharSelector()
end
2018-10-21 20:31:50 +00:00
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 = [[
2018-10-22 00:12:25 +00:00
    
      
    
     
     
2018-10-21 20:31:50 +00:00
2018-10-22 00:12:25 +00:00
Advanced Paint Program
2018-10-21 20:31:50 +00:00
by LDDestroier
or EldidiStroyrr
if you please!
2018-10-22 00:12:25 +00:00
PAIN is a multi-frame paint program with the intention of becoming a stable, well-used, and mondo-useful CC drawing utility.
2018-10-21 20:31:50 +00:00
2018-10-22 00:12:25 +00:00
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.
2018-10-21 20:31:50 +00:00
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.
]]
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.
2018-10-22 00:12:25 +00:00
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.
2018-10-21 20:31:50 +00:00
]]
guiHelp(helpText)
end
local menuPoses = {}
local menuFunctions = {
[1] = function() --File
while true do
--renderAllPAIN()
local output, longestLen = makeSubMenu(1,cleary-1,{"Save","Save As","Export",((peripheral.find("printer")) and "Print" or nil)})
doRender = true
if output == 1 then --Save
local _fname = fileExport(_,defaultSaveFormat,fileName)
if _fname then
barmsg = "Saved as '".._fname.."'"
lastPaintEncoded = deepCopy(paintEncoded)
changedImage = false
end
break
elseif output == 2 then --Save As
local oldfilename = fileName
fileName = nil
local res = fileExport(_,defaultSaveFormat)
if not res then
fileName = oldfilename
end
barmsg = "Saved as '"..fileName.."'"
elseif output == 3 then --Export
local res = fileExport(longestLen+1)
if res then
barmsg = "Exported as '"..res.."'"
break
end
elseif output == 4 then --Print
filePrint()
break
elseif output == false then
return "nobreak"
end
reRenderPAIN()
end
end,
[2] = function() --Edit
2018-10-21 20:48:38 +00:00
local output = makeSubMenu(6,cleary-1,{"Delete Frame","Clear Frame","Crop Frame","Choose Box Character","Choose Special Character","BLittle Shrink"})
2018-10-21 20:31:50 +00:00
doRender = true
if output == 1 then
editDelFrame()
elseif output == 2 then
editClear()
elseif output == 3 then
editCrop()
elseif output == 4 then
editBoxCharSelector()
elseif output == 5 then
2018-10-21 20:48:38 +00:00
editSpecialCharSelector()
elseif output == 6 then
2018-10-21 20:31:50 +00:00
local res = bottomPrompt("You sure? It's unreversable! (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()
doRender = true
barmsg = "Shrunk image."
end
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"})
doRender = true
if output == 1 then
windowSetScrSize()
elseif output == 2 then
gotoCoords()
elseif output == 3 then
rendback.b = paint.b
rendback.t = paint.t
doRender = true
elseif output == false then
return "nobreak"
end
end,
[4] = function() --About
local output = makeSubMenu(17,cleary-1,{"PAIN","File Formats","Help!"})
doRender = true
if output == 1 then
aboutPAIN()
elseif output == 2 then
aboutFileFormats()
elseif output == 3 then
guiHelp()
doRender = true
end
end,
[5] = function() --Exit
if changedImage then
local outcum = bottomPrompt("Abandon unsaved work? (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl})
sleep(0)
if outcum == "y" then
return "exit"
else
doRender = true
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]()
if res == "exit" then
return "exit"
elseif res == "nobreak" then
reRenderPAIN()
else
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
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]()
coroutine.yield()
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)
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)
getEvents("char","mouse_click")
doRender = true
end
local getInput = function() --gotta catch them all
local button, x, y, oldmx, oldmy, origx, origy
local isDragging = false
local proceed = false
renderBar(barmsg)
while true do
doRender = false
local oldx,oldy = paint.scrollX,paint.scrollY
local evt = {getEvents("mouse_scroll","mouse_click", "mouse_drag","mouse_up","key","key_up",true)}
if (evt[1] == "mouse_scroll") and (not viewing) then
local 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 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
doRender = true
if y <= scr_y-(renderBlittle and 0 or doRenderBar) then
if (button == 3) then
putDownText(x,y)
miceDown = {}
keysDown = {}
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}
elseif (not dontDragThisTime) then
if evt[1] == "mouse_drag" then
local 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
changedImage = true
end
dontDragThisTime = false
elseif button == 2 and y <= scr_y-(renderBlittle and 0 or doRenderBar) then
deleteDot(x+paint.scrollX,y+paint.scrollY)
changedImage = true
end
elseif origy >= scr_y-(renderBlittle and 0 or doRenderBar) then
keysDown = {}
local res = displayMenu()
if res == "exit" then break end
doRender = true
end
elseif (evt[1] == "mouse_up") and (not viewing) then
origx,origy = 0,0
local button = evt[2]
miceDown[button] = false
oldmx,oldmy = nil,nil
lastMX, lastMY = nil,nil
if isDragging then
local 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
changedImage = true
doRender = true
end
saveToUndoBuffer()
isDragging = false
elseif evt[1] == "key" then
local key = evt[2]
if (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
evenDrawGrid = not evenDrawGrid
else
doRenderBar = math.abs(doRenderBar-1)
end
doRender = true
end
if key == keys.b then
local blTerm, oldTerm = getBlittle()
renderBlittle = not renderBlittle
isDragging = false
term.setBackgroundColor(rendback.b)
term.clear()
if 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 (key == keys.c) and (not renderBlittle) then
gotoCoords()
resetInputState()
doRender = true
end
if (keysDown[keys.leftShift]) and (not isDragging) then
if key == keys.left then
paintEncoded[frame] = movePaintEncoded(paintEncoded[frame],-1,0)
saveToUndoBuffer()
doRender = true
changedImage = true
elseif key == keys.right then
paintEncoded[frame] = movePaintEncoded(paintEncoded[frame],1,0)
saveToUndoBuffer()
doRender = true
changedImage = true
elseif key == keys.up then
paintEncoded[frame] = movePaintEncoded(paintEncoded[frame],0,-1)
saveToUndoBuffer()
doRender = true
changedImage = true
elseif key == keys.down then
paintEncoded[frame] = movePaintEncoded(paintEncoded[frame],0,1)
saveToUndoBuffer()
doRender = true
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
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
changedImage = true
saveToUndoBuffer()
end
end
end
if not keysDown[keys.leftAlt] then
if key == keys.equals then --basically 'plus'
if 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.leftShift] then
paintEncoded[frame+1] = deepCopy(sheet)
end
end
frame = frame + 1
end
saveToUndoBuffer()
doRender = true
changedImage = true
elseif key == keys.minus then
if 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
changedImage = true
end
end
if not 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
bepimode = not 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[2] == 1 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 renderBlittle then
x = 2*x
y = 3*y
end
putDownText(x,y)
miceDown = {}
keysDown = {}
end
doRender = true
changedImage = true
isDragging = false
end
2018-10-25 23:05:37 +00:00
if key == keys.f and not (keysDown[keys.leftShift] or keysDown[keys.rightShift]) then
2018-10-25 23:10:10 +00:00
renderBottomBar("Click to fill area.")
2018-10-25 23:05:37 +00:00
local mevt
repeat
mevt = {os.pullEvent()}
until (mevt[1] == "key" and mevt[2] == keys.x) or (mevt[2] == 1 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 renderBlittle then
x = 2*x
y = 3*y
end
fillTool(paintEncoded[frame], x, y, paint)
miceDown = {}
keysDown = {}
end
doRender = true
changedImage = true
isDragging = false
renderBottomBar("Click to fill region.")
end
2018-10-21 20:31:50 +00:00
if key == keys.p then
renderBottomBar("Pick color with cursor:")
paintEncoded = clearAllRedundant(paintEncoded)
local mevt
repeat
mevt = {os.pullEvent()}
until (mevt[1] == "key" and mevt[2] == keys.x) or (mevt[2] == 1 and mevt[4] <= scr_y)
if not (mevt[1] == "key" and mevt[2] == keys.x) then
local x, y = mevt[3]+paint.scrollX, mevt[4]+paint.scrollY
if 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
miceDown = {}
keysDown = {}
doRender = true
isDragging = false
break
end
end
miceDown = {}
keysDown = {}
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 = {}
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
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
2018-10-21 20:48:38 +00:00
if keysDown[keys.leftShift] then
paint.c = specialCharSelector()
else
paint.c = boxCharSelector()
end
2018-10-21 20:31:50 +00:00
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 undoPos < #undoBuffer then
doRedo()
barmsg = "Redood."
doRender = true
elseif 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 doRender then
renderAllPAIN()
doRender = false
end
end
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
runPainEditor = function(...) --needs to be cleaned up
local tArg = table.pack(...)
if not (tArg[1] == "-n" or (not tArg[1])) then
fileName = shell.resolve(tostring(tArg[1]))
end
if not fileName then
paintEncoded = {{}}
elseif not fs.exists(fileName) then
local ex = fileName:sub(-4):lower()
if ex == ".nfp" then
defaultSaveFormat = 1
elseif ex == ".nft" then
defaultSaveFormat = 2
elseif ex == ".blt" then
defaultSaveFormat = 3
elseif ex == ".gif" then
defaultSaveFormat = 5
elseif ex == ".ucg" then
defaultSaveFormat = 6
else
defaultSaveFormat = 4
end
paintEncoded = {{}}
elseif fs.isDir(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(" FUCKING")
sleep(0.4)
print(" FOLDER.")
term.setTextColor(colors.white)
sleep(0.2)
print("You crazy person.")
sleep(0.2)
else
print("That's a folder.")
end
return
else
local file = fs.open(fileName,"r")
local contents = file.readAll()
file.close()
if type(tun(contents)) ~= "table" then
term.setTextColor(colors.white)
if contents:sub(1,3) == "BLT" then --thank you bomb bloke for this easy identifier
if pMode ~= 1 then print("Importing from BLT...") end
paintEncoded = importFromBLT(fileName)
defaultSaveFormat = 3
elseif contents:sub(1,2) == "BM" then
if pMode ~= 1 then print("Importing from BMP...") end
paintEncoded = {importFromBitmap(fileName)}
defaultSaveFormat = 4
elseif contents:sub(1,3) == "GIF" then
if pMode ~= 1 then print("Importing from GIF, this'll take a while...") end
paintEncoded = importFromGIF(fileName,true)
defaultSaveFormat = 5
elseif contents:sub(1,4) == "?!7\2" then
if pMode ~= 1 then print("Importing from UCG...") end
paintEncoded = {importFromUCG(fileName)}
defaultSaveFormat = 6
elseif contents:find(string.char(30)) and contents:find(string.char(31)) then
if pMode ~= 1 then print("Importing from NFT...") end
defaultSaveFormat = 2
paintEncoded = {importFromNFT(contents)}
elseif (checkIfNFP(contents) or readNonImageAsNFP) then
print("Importing from NFP...")
paintEncoded = {importFromPaint(contents)}
defaultSaveFormat = 1
else
return print("That is not a valid image file.")
end
else
paintEncoded = tun(contents)
defaultSaveFormat = 4
end
end
--paintEncoded = tun(tse(paintEncoded):gsub("bg","b"):gsub("txt","t"):gsub("char","c"):gsub("meta","m")) -- gotta have backwards compatibility, sorta, okay maybe not
if not paintEncoded[frame] then paintEncoded = {paintEncoded} end
if 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)
return
else
renderPAIN(paintEncoded[frame],paint.scrollX,paint.scrollY,true)
end
lastPaintEncoded = deepCopy(paintEncoded)
undoBuffer = {deepCopy(paintEncoded)}
parallel.waitForAny(getInput,doNonEventDrivenMovement)
term.setCursorPos(1,scr_y)
term.setBackgroundColor(colors.black)
term.clearLine()
end
if not shell then return end
runPainEditor(...)