local PIXELBOX = {}
local OBJECT = {}
local api = {}
local graphic = {}
local chars = "0123456789abcdef"
graphic.to_blit = {}
for i = 0, 15 do
graphic.to_blit[2^i] = chars:sub(i + 1, i + 1)
end
local function build_graphode(terminal)
local BOX = PIXELBOX.new(terminal)
BOX:clear(colors.black)
BOX:push_updates()
local _MODE_ = 0
local _FROZEN_ = false
local PALETTE = {
[0]={},
[2]={}
}
PALETTE[1] = PALETTE[0]
for i=0,15 do
local c = {terminal.getPaletteColor(2^i)}
PALETTE[0][2^i],PALETTE[2][i] = c,c
end
terminal.grapode = true
local GETSIZE = terminal.getSize
local SETPALETTE = terminal.setPaletteColor
local CLEAR = terminal.clear
if not terminal.graphode then
terminal.graphode = true
function terminal.setGraphicsMode(mode)
if type(mode) == "boolean" then
if _MODE_ == 0 and mode ~= _MODE_ then api.update(BOX) end
_MODE_ = mode and 1 or 0
elseif type(mode) == "number" then
if _MODE_ == 0 and mode ~= _MODE_ then api.update(BOX) end
if mode >= 0 and mode <= 3 then
_MODE_ = mode
else
error("Invalid mode: "..mode,2)
end
end
end
function terminal.getGraphicsMode()
return not (_MODE_ == 0) and _MODE_ or false
end
function terminal.getSize(mode)
local w,h = GETSIZE()
if type(mode) == "number" and mode > 0 or mode == true then
return w*2,h*3
else
return w,h
end
end
function terminal.setPaletteColor(color,r_hex,g,b)
local rgb = {r_hex,g,b}
if r_hex and not g and not b then
rgb = {api.hex_to_palette(r_hex)}
end
if _MODE_ == 0 or _MODE_ == 1 then
PALETTE[0][color] = rgb
PALETTE[2][math.log(color,2)] = rgb
elseif _MODE_ == 2 then
if color >= 0 and color <= 255 then
if color >= 0 and color <= 15 then
PALETTE[0][2^color] = rgb
end
PALETTE[2][color] = rgb
else
error("Invalid color: "..color)
end
end
api.update_palette(SETPALETTE, PALETTE[0])
end
function terminal.getPaletteColor(color)
return table.unpack(PALETTE[(_MODE_ == 0 or _MODE_ == 1) and 0 or 2][color])
end
function terminal.setFrozen(frozen)
if _FROZEN_ and frozen == false then
api.update(BOX)
end
(type(frozen) == "boolean"
and function() _FROZEN_ = not not frozen end
or function() end)()
end
function terminal.clear()
if _MODE_ == 0 then CLEAR()
else
BOX:clear(colors.black)
end
end
function terminal.setPixel(x,y,color)
if _MODE_ == 1 or _MODE_ == 0 then
BOX:set_pixel(x,y,color)
elseif _MODE_ == 2 then
local rgb = PALETTE[2][color]
_G.rgb = rgb
local c = api.get_closest_color(PALETTE[1],rgb)
BOX:set_pixel(x,y,c)
else return end
if not _FROZEN_ and _MODE_ > 0 then
api.update(BOX)
end
end
function terminal.getFrozen()
return _FROZEN_
end
end
end
function PIXELBOX.INDEX_SYMBOL_CORDINATION(tbl,x,y,val)
tbl[x+y*2-2] = val
return tbl
end
function OBJECT:within(x,y)
return x > 0
and y > 0
and x <= self.width*2
and y <= self.height*3
end
function PIXELBOX.RESTORE(BOX,color)
BOX.CANVAS = api.createNDarray(1)
BOX.UPDATES = api.createNDarray(1)
BOX.CHARS = api.createNDarray(1)
for y=1,BOX.height*3 do
for x=1,BOX.width*2 do
BOX.CANVAS[y][x] = color
end
end
for y=1,BOX.height do
for x=1,BOX.width do
BOX.CHARS[y][x] = {symbol=" ",background=graphic.to_blit[color],fg="f"}
end
end
getmetatable(BOX.CANVAS).__tostring = function() return "PixelBOX_SCREEN_BUFFER" end
end
function OBJECT:push_updates()
self.symbols = api.createNDarray(2)
self.lines = api.create_blit_array(self.height)
local SYMBOL_COLORS = api.createNDarray(1)
local SYMBOL_LUT = api.createNDarray(2)
getmetatable(self.symbols).__tostring=function() return "PixelBOX.SYMBOL_BUFFER" end
setmetatable(self.lines,{__tostring=function() return "PixelBOX.LINE_BUFFER" end})
for y,x_list in pairs(self.CANVAS) do
for x,block_color in pairs(x_list) do
local RELATIVE_X = math.ceil(x/2)
local RELATIVE_Y = math.ceil(y/3)
if self.UPDATES[RELATIVE_Y][RELATIVE_X] then
local SYMBOL_POS_X = (x-1)%2+1
local SYMBOL_POS_Y = (y-1)%3+1
if not SYMBOL_LUT[RELATIVE_Y][RELATIVE_X][block_color] then
if not SYMBOL_COLORS[RELATIVE_Y][RELATIVE_X] then SYMBOL_COLORS[RELATIVE_Y][RELATIVE_X] = 0 end
SYMBOL_COLORS[RELATIVE_Y][RELATIVE_X] = SYMBOL_COLORS[RELATIVE_Y][RELATIVE_X] + 1
SYMBOL_LUT[RELATIVE_Y][RELATIVE_X][block_color] = true
end
self.symbols[RELATIVE_Y][RELATIVE_X] = PIXELBOX.INDEX_SYMBOL_CORDINATION(
self.symbols[RELATIVE_Y][RELATIVE_X],
SYMBOL_POS_X,SYMBOL_POS_Y,
block_color
)
end
end
end
for y=1,self.height do
for x=1,self.width do
local color_block = self.symbols[y][x]
if self.UPDATES[y][x] then
local char,fg,bg = " ",colors.black,color_block[1]
if SYMBOL_COLORS[y][x] > 1 then
char,fg,bg = graphic.build_drawing_char(color_block)
end
self.CHARS[y][x] = {symbol=char, background=graphic.to_blit[bg], fg=graphic.to_blit[fg]}
self.lines[y] = {
self.lines[y][1]..char,
self.lines[y][2]..graphic.to_blit[fg],
self.lines[y][3]..graphic.to_blit[bg]
}
else
local prev_data = self.CHARS[y][x]
self.lines[y] = {
self.lines[y][1]..prev_data.symbol,
self.lines[y][2]..prev_data.fg,
self.lines[y][3]..prev_data.background
}
end
end
end
self.UPDATES = api.createNDarray(1)
end
function OBJECT:get_pixel(x,y)
return self.CANVAS[y][x]
end
function OBJECT:clear(color)
PIXELBOX.RESTORE(self,color)
end
function OBJECT:draw()
for y,line in ipairs(self.lines) do
self.term.setCursorPos(1,y)
self.term.blit(
table.unpack(line)
)
end
end
function OBJECT:set_pixel(x,y,color)
local RELATIVE_X = math.ceil((x+1)/2)
local RELATIVE_Y = math.ceil((y+1)/3)
self.CANVAS[y+1][x+1] = color
self.UPDATES[RELATIVE_Y][RELATIVE_X] = true
end
function PIXELBOX.new(terminal,bg,existing)
local bg = bg or terminal.getBackgroundColor() or colors.black
local BOX = {}
local w,h = terminal.getSize()
BOX.term = terminal
BOX.width = w
BOX.height = h
PIXELBOX.RESTORE(BOX,bg,existing)
return setmetatable(BOX,{__index = OBJECT})
end
function api.createNDarray(n, tbl)
tbl = tbl or {}
if n == 0 then return tbl end
setmetatable(tbl, {__index = function(t, k)
local new = api.createNDarray(n - 1)
t[k] = new
return new
end})
return tbl
end
function api.create_blit_array(count)
local out = {}
for i=1,count do
out[i] = {"","",""}
end
return out
end
function api.get_closest_color(palette,c)
local result = {}
for k,v in pairs(palette) do
table.insert(result,{
dist=math.sqrt(
(v[1]-c[1])^2 +
(v[2]-c[2])^2 +
(v[3]-c[3])^2
), color=k
})
end
table.sort(result,function(a,b) return a.dist < b.dist end)
return result[1].color
end
function api.convert_color_255(r,g,b)
return r*255,g*255,b*255
end
function api.hex_to_palette(hex)
local r = (math.floor(hex/0x10000)%256)/255
local g = (math.floor(hex/0x100)%256)/255
local b = (hex%256)/255
return r,g,b
end
function api.update_palette(updater,palette)
for k,v in pairs(palette) do
updater(k,table.unpack(v))
end
end
function api.update(box)
box:push_updates()
box:draw()
end
function graphic.build_drawing_char(arr,mode)
local cols,fin,char,visited = {},{},{},{}
local entries = 0
for k,v in pairs(arr) do
cols[v] = cols[v] ~= nil and
{count=cols[v].count+1,c=cols[v].c}
or (function() entries = entries + 1 return {count=1,c=v} end)()
end
for k,v in pairs(cols) do
if not visited[v.c] then
visited[v.c] = true
if entries == 1 then table.insert(fin,v) end
table.insert(fin,v)
end
end
table.sort(fin,function(a,b) return a.count > b.count end)
local swap = true
for k=1,6 do
if arr[k] == fin[1].c then char[k] = 1
elseif arr[k] == fin[2].c then char[k] = 0
else char[k] =(function()
swap = not swap
return swap and 1 or 0
end)() end
end
if char[6] == 1 then for i = 1, 5 do char[i] = 1-char[i] end end
local n = 128
for i = 0, 4 do n = n + char[i+1]*2^i end
return string.char(n),char[6] == 1 and fin[2].c or fin[1].c,char[6] == 1 and fin[1].c or fin[2].c
end
build_graphode(term)