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