· 4 years ago · Mar 20, 2021, 02:50 PM
1-- Paint created by nitrogenfingers (edited by dan200)
2-- http://www.youtube.com/user/NitrogenFingers
3
4------------
5-- Fields --
6------------
7
8-- The width and height of the terminal
9local w, h = term.getSize()
10
11-- Cursor position
12local curx, cury = 1, 1
13
14term.setCursorBlink(true)
15
16-- The selected color, to draw in a line or not, and the color of the canvas
17local selectedcolor = colors.white
18local canvascolor = colors.black
19local keepDrawing = false
20
21-- The values stored in the canvas
22local canvas = {}
23
24-- The menu options
25local mChoices = { "Save", "Exit" }
26
27-- The message displayed in the footer bar
28local fMessage = "Press Ctrl to access menu"
29
30-------------------------
31-- Initialisation --
32-------------------------
33
34-- Determines if the file exists, and can be edited on this computer
35local tArgs = { ... }
36if #tArgs == 0 then
37 if arg then
38 local programName = arg[0] or fs.getName(shell.getRunningProgram())
39 print("Usage: " .. programName .. " <path>")
40 return
41 else
42 local i = 0
43 repeat
44 tArgs[1] = fs.getDir(shell.getRunningProgram()).."image"..i..".nfp"
45 i = i + 1
46 until not fs.exists(tArgs[1])
47 end
48
49end
50local sPath = shell.resolve(tArgs[1])
51local bReadOnly = fs.isReadOnly(sPath)
52if fs.exists(sPath) and fs.isDir(sPath) then
53 print("Cannot edit a directory.")
54 return
55end
56
57-- Create .nfp files by default
58if not fs.exists(sPath) and not string.find(sPath, "%.") then
59 local sExtension = settings.get("paint.default_extension")
60 if sExtension ~= "" and type(sExtension) == "string" then
61 sPath = sPath .. "." .. sExtension
62 end
63end
64
65
66---------------
67-- Functions --
68---------------
69
70local function getCanvasPixel(x, y)
71 if canvas[y] then
72 return canvas[y][x]
73 end
74 return nil
75end
76
77--[[
78 Converts a color value to a text character
79 params: color = the number to convert to a hex value
80 returns: a string representing the chosen color
81]]
82local function getCharOf(color)
83 -- Incorrect values always convert to nil
84 if type(color) == "number" then
85 local value = math.floor(math.log(color) / math.log(2)) + 1
86 if value >= 1 and value <= 16 then
87 return string.sub("0123456789abcdef", value, value)
88 end
89 end
90 return " "
91end
92
93--[[
94 Converts a text character to color value
95 params: char = the char (from string.byte) to convert to number
96 returns: the color number of the hex value
97]]
98local tcolorLookup = {}
99for n = 1, 16 do
100 tcolorLookup[string.byte("0123456789abcdef", n, n)] = 2 ^ (n - 1)
101end
102local function getcolorOf(char)
103 -- Values not in the hex table are transparent (canvas colored)
104 return tcolorLookup[char]
105end
106
107--[[
108 Loads the file into the canvas
109 params: path = the path of the file to open
110 returns: nil
111]]
112local function load(path)
113 -- Load the file
114 if fs.exists(path) then
115 local file = fs.open(sPath, "r")
116 local sLine = file.readLine()
117 while sLine do
118 local line = {}
119 for x = 1, w - 2 do
120 line[x] = getcolorOf(string.byte(sLine, x, x))
121 end
122 table.insert(canvas, line)
123 sLine = file.readLine()
124 end
125 file.close()
126 end
127end
128
129--[[
130 Saves the current canvas to file
131 params: path = the path of the file to save
132 returns: true if save was successful, false otherwise
133]]
134local function save(path)
135 -- Open file
136 local sDir = string.sub(sPath, 1, #sPath - #fs.getName(sPath))
137 if not fs.exists(sDir) then
138 fs.makeDir(sDir)
139 end
140
141 local file, err = fs.open(path, "w")
142 if not file then
143 return false, err
144 end
145
146 -- Encode (and trim)
147 local tLines = {}
148 local nLastLine = 0
149 for y = 1, h - 1 do
150 local sLine = ""
151 local nLastChar = 0
152 for x = 1, w - 2 do
153 local c = getCharOf(getCanvasPixel(x, y))
154 sLine = sLine .. c
155 if c ~= " " then
156 nLastChar = x
157 end
158 end
159 sLine = string.sub(sLine, 1, nLastChar)
160 tLines[y] = sLine
161 if #sLine > 0 then
162 nLastLine = y
163 end
164 end
165
166 -- Save out
167 for n = 1, nLastLine do
168 file.writeLine(tLines[n])
169 end
170 file.close()
171 return true
172end
173
174--[[
175 Converts a single pixel of a single line of the canvas and draws it
176 returns: nil
177]]
178local function drawCanvasPixel(x, y, col)
179 local pixel = getCanvasPixel(x, y)
180 if col then pixel = col end
181 if pixel then
182 term.setCursorPos(x, y)
183 term.setBackgroundColour(pixel)
184 term.write(" ")
185 term.setCursorPos(x, y)
186 else
187 term.setBackgroundColour(colors.black)
188 term.setTextColour(colors.gray)
189 term.setCursorPos(x, y)
190 term.write("\127")
191 end
192end
193
194local color_hex_lookup = {}
195for i = 0, 15 do
196 color_hex_lookup[2 ^ i] = string.format("%x", i)
197end
198
199--[[
200 Draws color picker sidebar, the pallette and the footer
201 returns: nil
202]]
203local function drawInterface()
204 -- Footer
205 term.setCursorPos(1, h)
206 term.setBackgroundColor(colors.black)
207 term.setTextColor(colors.yellow)
208 term.clearLine()
209 term.write(fMessage)
210
211 -- color Picker
212 for i = 1, 16 do
213 term.setTextColor(colors.white)
214 term.setBackgroundColor(colors.black)
215 term.setCursorPos(w - 1, i)
216 term.write(color_hex_lookup[2 ^ (i - 1)])
217 drawCanvasPixel(w, i, 2 ^ (i - 1))
218 end
219
220 term.setCursorPos(w - 1, 17)
221 term.blit("n\127", "07", "ff")
222 term.setBackgroundColor(canvascolor)
223 term.setCursorPos(w-1, 18)
224 term.write("!")
225 drawCanvasPixel(w, 18, selectedcolor)
226 term.setTextColor(colors.white)
227 term.setBackgroundColor(canvascolor)
228 term.setCursorPos(w-1, 19)
229 term.write(" \n \n \n ")
230 term.setCursorPos(w-1, 19)
231 term.write(curx)
232 term.setCursorPos(w-1, 20)
233 term.write(cury)
234 term.setCursorPos(w-1, 21)
235 term.write("L")
236 term.setBackgroundColor((keepDrawing and colors.green or colors.red))
237 term.write((keepDrawing and "Y" or "N"))
238
239 -- Padding
240 term.setBackgroundColor(canvascolor)
241 for i = 22, h - 1 do
242 term.setCursorPos(w - 1, i)
243 term.write(" ")
244 end
245 -- return cursor to previous position
246 term.setCursorPos(curx, cury)
247end
248
249--[[
250 Converts each color in a single line of the canvas and draws it
251 returns: nil
252]]
253local function drawCanvasLine(y)
254 local text, fg, bg = "", "", ""
255 for x = 1, w - 2 do
256 drawCanvasPixel(x, y)
257 end
258
259end
260
261--[[
262 Converts each color in the canvas and draws it
263 returns: nil
264]]
265local function drawCanvas()
266 for y = 1, h - 1 do
267 drawCanvasLine(y)
268 end
269end
270
271local menu_choices = {
272 Save = function()
273 if bReadOnly then
274 fMessage = "Access denied"
275 return false
276 end
277 local success, err = save(sPath)
278 if success then
279 fMessage = "Saved to " .. sPath
280 else
281 if err then
282 fMessage = "Error saving to " .. err
283 else
284 fMessage = "Error saving to " .. sPath
285 end
286 end
287 return false
288 end,
289 Exit = function()
290 return true
291 end,
292}
293
294--[[
295 Draws menu options and handles input from within the menu.
296 returns: true if the program is to be exited; false otherwise
297]]
298local function accessMenu()
299 -- Selected menu option
300 local selection = 1
301
302 term.setBackgroundColor(colors.black)
303
304 while true do
305 -- Draw the menu
306 term.setCursorPos(1, h)
307 term.clearLine()
308 term.setTextColor(colors.white)
309 for k, v in pairs(mChoices) do
310 if selection == k then
311 term.setTextColor(colors.yellow)
312 term.write("[")
313 term.setTextColor(colors.white)
314 term.write(v)
315 term.setTextColor(colors.yellow)
316 term.write("]")
317 term.setTextColor(colors.white)
318 else
319 term.write(" " .. v .. " ")
320 end
321 end
322
323 -- Handle input in the menu
324 local id, param1, param2, param3 = os.pullEvent()
325 if id == "key" then
326 local key = param1
327
328 -- Handle menu shortcuts.
329 for _, menu_item in ipairs(mChoices) do
330 local k = keys[menu_item:sub(1, 1):lower()]
331 if k and k == key then
332 return menu_choices[menu_item]()
333 end
334 end
335
336 if key == keys.right then
337 -- Move right
338 selection = selection + 1
339 if selection > #mChoices then
340 selection = 1
341 end
342
343 elseif key == keys.left and selection > 1 then
344 -- Move left
345 selection = selection - 1
346 if selection < 1 then
347 selection = #mChoices
348 end
349
350 elseif key == keys.enter then
351 -- Select an option
352 return menu_choices[mChoices[selection]]()
353 elseif key == keys.leftCtrl or keys == keys.rightCtrl then
354 -- Cancel the menu
355 return false
356 end
357 end
358 end
359end
360
361--[[
362 Runs the main thread of execution. Draws the canvas and interface, and handles key events.
363 returns: nil
364]]
365
366local function handleEvents()
367 local programActive = true
368 while programActive do
369 local id, p1, p2, p3 = os.pullEvent()
370 if id == "key" then
371 if p1 == keys.leftCtrl or p1 == keys.rightCtrl then
372 programActive = not accessMenu()
373 drawInterface()
374 end
375 if p1 == keys.space then
376 drawCanvasPixel(curx, cury, selectedcolor)
377 if not canvas[cury] then
378 canvas[cury] = {}
379 end
380 canvas[cury][curx] = selectedcolor
381 end
382 if p1 == keys.capsLock then
383 keepDrawing = not keepDrawing
384 end
385 if p1 == keys.left then
386 curx = math.max(curx - 1, 1)
387 elseif p1 == keys.right then
388 curx = math.min(curx + 1, w)
389 end
390 if p1 == keys.up then
391 cury = math.max(cury - 1, 1)
392 elseif p1 == keys.down then
393 cury = math.min(cury + 1, h)
394 end
395 -- COLORS
396 if p1 == keys.a then
397 selectedcolor = 2^10
398 elseif p1 == keys.b then
399 selectedcolor = 2^11
400 elseif p1 == keys.c then
401 selectedcolor = 2^12
402 elseif p1 == keys.d then
403 selectedcolor = 2^13
404 elseif p1 == keys.e then
405 selectedcolor = 2^14
406 elseif p1 == keys.f then
407 selectedcolor = 2^15
408 elseif p1 == keys.n then
409 selectedcolor = nil
410 elseif p1 == keys.zero or p1 == keys.numPad0 then
411 selectedcolor = 2^0
412 elseif p1 == keys.one or p1 == keys.numPad1 then
413 selectedcolor = 2^1
414 elseif p1 == keys.two or p1 == keys.numPad2 then
415 selectedcolor = 2^2
416 elseif p1 == keys.three or p1 == keys.numPad3 then
417 selectedcolor = 2^3
418 elseif p1 == keys.four or p1 == keys.numPad4 then
419 selectedcolor = 2^4
420 elseif p1 == keys.five or p1 == keys.numPad5 then
421 selectedcolor = 2^5
422 elseif p1 == keys.six or p1 == keys.numPad6 then
423 selectedcolor = 2^6
424 elseif p1 == keys.seven or p1 == keys.numPad7 then
425 selectedcolor = 2^7
426 elseif p1 == keys.eight or p1 == keys.numPad8 then
427 selectedcolor = 2^8
428 elseif p1 == keys.nine or p1 == keys.numPad9 then
429 selectedcolor = 2^9
430 end
431 term.setCursorPos(curx, cury)
432 elseif id == "term_resize" then
433 w, h = term.getSize()
434 drawCanvas()
435 drawInterface()
436 end
437 if keepDrawing then drawCanvasPixel(curx, cury, selectedcolor); if not canvas[cury] then canvas[cury] = {} end; canvas[cury][curx] = selectedcolor end
438 drawCanvasPixel(w, 18, selectedcolor)
439 term.setTextColor(colors.white)
440 term.setBackgroundColor(canvascolor)
441 term.setCursorPos(w-1, 19)
442 term.write(" \n \n \n ")
443 term.setCursorPos(w-1, 19)
444 term.write(curx)
445 term.setCursorPos(w-1, 20)
446 term.write(cury)
447 term.setCursorPos(w, 21)
448 term.setBackgroundColor((keepDrawing and colors.green or colors.red))
449 term.write((keepDrawing and "Y" or "N"))
450 term.setCursorPos(curx, cury)
451 end
452end
453
454-- Init
455load(sPath)
456drawCanvas()
457drawInterface()
458term.setCursorPos(curx, cury)
459
460-- Main loop
461handleEvents()
462
463-- Shutdown
464term.setBackgroundColor(colors.black)
465term.setTextColor(colors.white)
466term.clear()
467term.setCursorPos(1, 1)