--- An API for advanced systems which can draw pixels and lines, load and draw -- image files. You can use the `colors` API for easier color manipulation. -- -- @module paintutils local expect = dofile("rom/modules/main/cc/expect.lua").expect local function drawPixelInternal( xPos, yPos ) term.setCursorPos( xPos, yPos ) term.write(" ") end local tColourLookup = {} for n = 1, 16 do tColourLookup[ string.byte( "0123456789abcdef", n, n ) ] = 2 ^ (n - 1) end local function parseLine( tImageArg, sLine ) local tLine = {} for x = 1, sLine:len() do tLine[x] = tColourLookup[ string.byte(sLine, x, x) ] or 0 end table.insert( tImageArg, tLine ) end --- Parses an image from a multi-line string -- -- @tparam string image The string containing the raw-image data. -- @treturn table The parsed image data, suitable for use with -- @{paintutils.drawImage}. function parseImage( image ) expect(1, image, "string") local tImage = {} for sLine in ( image .. "\n" ):gmatch( "(.-)\n" ) do parseLine( tImage, sLine ) end return tImage end --- Loads an image from a file. -- -- You can create a file suitable for being loaded using the `paint` program. -- -- @tparam string path The file to load. -- -- @treturn table|nil The parsed image data, suitable for use with -- @{paintutils.drawImage}, or `nil` if the file does not exist. function loadImage( path ) expect(1, path, "string") if fs.exists( path ) then local file = io.open( path, "r" ) local sContent = file:read("*a") file:close() return parseImage( sContent ) end return nil end --- Draws a single pixel to the current term at the specified position. -- -- Be warned, this may change the position of the cursor and the current -- background colour. You should not expect either to be preserved. -- -- @tparam number xPos The x position to draw at, where 1 is the far left. -- @tparam number yPos The y position to draw at, where 1 is the very top. -- @tparam[opt] number colour The @{colors|color} of this pixel. This will be -- the current background colour if not specified. function drawPixel( xPos, yPos, colour ) expect(1, xPos, "number") expect(2, yPos, "number") expect(3, colour, "number", "nil") if type( xPos ) ~= "number" then error( "bad argument #1 (expected number, got " .. type( xPos ) .. ")", 2 ) end if type( yPos ) ~= "number" then error( "bad argument #2 (expected number, got " .. type( yPos ) .. ")", 2 ) end if colour ~= nil and type( colour ) ~= "number" then error( "bad argument #3 (expected number, got " .. type( colour ) .. ")", 2 ) end if colour then term.setBackgroundColor( colour ) end return drawPixelInternal( xPos, yPos ) end --- Draws a straight line from the start to end position. -- -- Be warned, this may change the position of the cursor and the current -- background colour. You should not expect either to be preserved. -- -- @tparam number startX The starting x position of the line. -- @tparam number startY The starting y position of the line. -- @tparam number endX The end x position of the line. -- @tparam number endY The end y position of the line. -- @tparam[opt] number colour The @{colors|color} of this pixel. This will be -- the current background colour if not specified. function drawLine( startX, startY, endX, endY, colour ) expect(1, startX, "number") expect(2, startY, "number") expect(3, endX, "number") expect(4, endY, "number") expect(5, colour, "number", "nil") startX = math.floor(startX) startY = math.floor(startY) endX = math.floor(endX) endY = math.floor(endY) if colour then term.setBackgroundColor( colour ) end if startX == endX and startY == endY then drawPixelInternal( startX, startY ) return end local minX = math.min( startX, endX ) local maxX, minY, maxY if minX == startX then minY = startY maxX = endX maxY = endY else minY = endY maxX = startX maxY = startY end -- TODO: clip to screen rectangle? local xDiff = maxX - minX local yDiff = maxY - minY if xDiff > math.abs(yDiff) then local y = minY local dy = yDiff / xDiff for x = minX, maxX do drawPixelInternal( x, math.floor( y + 0.5 ) ) y = y + dy end else local x = minX local dx = xDiff / yDiff if maxY >= minY then for y = minY, maxY do drawPixelInternal( math.floor( x + 0.5 ), y ) x = x + dx end else for y = minY, maxY, -1 do drawPixelInternal( math.floor( x + 0.5 ), y ) x = x - dx end end end end --- Draws the outline of a box on the current term from the specified start -- position to the specified end position. -- -- Be warned, this may change the position of the cursor and the current -- background colour. You should not expect either to be preserved. -- -- @tparam number startX The starting x position of the line. -- @tparam number startY The starting y position of the line. -- @tparam number endX The end x position of the line. -- @tparam number endY The end y position of the line. -- @tparam[opt] number colour The @{colors|color} of this pixel. This will be -- the current background colour if not specified. function drawBox( startX, startY, endX, endY, nColour ) expect(1, startX, "number") expect(2, startY, "number") expect(3, endX, "number") expect(4, endY, "number") expect(5, nColour, "number", "nil") startX = math.floor(startX) startY = math.floor(startY) endX = math.floor(endX) endY = math.floor(endY) if nColour then term.setBackgroundColor( nColour ) end if startX == endX and startY == endY then drawPixelInternal( startX, startY ) return end local minX = math.min( startX, endX ) local maxX, minY, maxY if minX == startX then minY = startY maxX = endX maxY = endY else minY = endY maxX = startX maxY = startY end for x = minX, maxX do drawPixelInternal( x, minY ) drawPixelInternal( x, maxY ) end if maxY - minY >= 2 then for y = minY + 1, maxY - 1 do drawPixelInternal( minX, y ) drawPixelInternal( maxX, y ) end end end --- Draws a filled box on the current term from the specified start position to -- the specified end position. -- -- Be warned, this may change the position of the cursor and the current -- background colour. You should not expect either to be preserved. -- -- @tparam number startX The starting x position of the line. -- @tparam number startY The starting y position of the line. -- @tparam number endX The end x position of the line. -- @tparam number endY The end y position of the line. -- @tparam[opt] number colour The @{colors|color} of this pixel. This will be -- the current background colour if not specified. function drawFilledBox( startX, startY, endX, endY, nColour ) expect(1, startX, "number") expect(2, startY, "number") expect(3, endX, "number") expect(4, endY, "number") expect(5, nColour, "number", "nil") startX = math.floor(startX) startY = math.floor(startY) endX = math.floor(endX) endY = math.floor(endY) if nColour then term.setBackgroundColor( nColour ) end if startX == endX and startY == endY then drawPixelInternal( startX, startY ) return end local minX = math.min( startX, endX ) local maxX, minY, maxY if minX == startX then minY = startY maxX = endX maxY = endY else minY = endY maxX = startX maxY = startY end for x = minX, maxX do for y = minY, maxY do drawPixelInternal( x, y ) end end end --- Draw an image loaded by @{paintutils.parseImage} or @{paintutils.loadImage}. -- -- @tparam table image The parsed image data. -- @tparam number xPos The x position to start drawing at. -- @tparam number xPos The y position to start drawing at. function drawImage( image, xPos, yPos ) expect(1, image, "table") expect(2, xPos, "number") expect(3, yPos, "number") for y = 1, #image do local tLine = image[y] for x = 1, #tLine do if tLine[x] > 0 then term.setBackgroundColor( tLine[x] ) drawPixelInternal( x + xPos - 1, y + yPos - 1 ) end end end end