0.9.0 - Added a working rasterization library.

This library uses braille characters and GPU raw instructions to display generated bitmaps in real-time, in high resolutions. A demo is available from command rtest.
This commit is contained in:
Ponali
2025-05-02 08:47:09 +02:00
parent 640be9230d
commit 6b8bee249a
4 changed files with 496 additions and 32 deletions
+3 -1
View File
@@ -1 +1,3 @@
Ergonomics AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABB
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDD
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEFF
+89 -3
View File
@@ -1,5 +1,91 @@
local raster = import("raster") local raster = import("raster")
raster.drawPixel(4, 3, 0xFFFF00) raster.init()
raster.drawPixel(40, 34, nil, 0xFF00F)
raster.drawPixel(3, 1) --[[for i=4,20 do
raster.set(i,i)
raster.set(i,i+4,0xFF00FF)
end]]
--[[ for x=4,20 do
for y=4,20 do
if (x+y)%2==0 then
raster.set(x,y,0xFF00FF)
end
end
end ]]
local event = import("event")
local x=0
local y=0
local vx=1
local vy=1
local col = 0x808080
local i=0
while event.pull("key_down",0)==nil do
i = i + 1
raster.set(x,y,col)
x = x + vx
y = y + vy
if x>raster.displayWidth then
x=raster.displayWidth
vx = -math.abs(vx)
col = math.random(0,0xFFFFFF)
end
if x<1 then
x=1
vx = math.abs(vx)
col = math.random(0,0xFFFFFF)
end
if y>raster.displayHeight-6 then
y=raster.displayHeight-6
vy = -math.abs(vy)
col = math.random(0,0xFFFFFF)
end
if y<1 then
y=1
vy = math.abs(vy)
col = math.random(0,0xFFFFFF)
end
if i>10 and i%15>0 then
while true do
local tries=0
local dx,dy=math.random(1,raster.displayWidth),math.random(1,raster.displayHeight-6)
if raster.get(dx,dy)~=0 then
raster.set(dx,dy,0)
break
end
tries = tries + 1
if tries>20 then
break
end
end
end
if i%10==0 then
raster.update()
coroutine.yield()
end
end
--[[ for i=0,360,4 do
local angle = i/180*math.pi
if false then
local x1,y1,x2,y2=raster.displayWidth/2,raster.displayHeight/2,raster.displayWidth/2+math.sin(angle)*80,raster.displayHeight/2+math.cos(angle)*80
raster.fillEllipse(x1,y1,x2,y2,0xFF00FF)
raster.update()
raster.fillEllipse(x1,y1,x2,y2,0x000000)
else
local x,y,c=raster.displayWidth/2,raster.displayHeight/2,math.abs(math.sin(angle)*100)
raster.drawCircle(x,y,c,0xFF00FF)
raster.update()
raster.drawCircle(x,y,c,0x000000)
end
end ]]
raster.free()
termlib.cursorPosY=1
+1 -1
View File
@@ -1,7 +1,7 @@
local loadfile = ... local loadfile = ...
local filesystem = loadfile("/halyde/lib/filesystem.lua")(loadfile) local filesystem = loadfile("/halyde/lib/filesystem.lua")(loadfile)
_G._OSVERSION = "Halyde 0.8.3" _G._OSVERSION = "Halyde 0.9.0"
function _G.import(module, ...) function _G.import(module, ...)
local args = table.pack(...) local args = table.pack(...)
+403 -27
View File
@@ -1,35 +1,411 @@
local raster = {} local raster = {
["units"]={},
["defaultBackgroundColor"]=0x000000,
["defaultForegroundColor"]=0xFFFFFF,
["displayWidth"]=0,
["displayHeight"]=0,
["charWidth"]=0,
["charHeight"]=0
}
local ocelot = component.proxy(component.list("ocelot")()) -- local ocelot = component.proxy(component.list("ocelot")())
local gpu = component.proxy(component.list("gpu")()) local gpu = component.proxy(component.list("gpu")())
function raster.drawPixel(x, y, newbg, newfg) local display = {}
-- get original character for "merging" local chunksAffected = {}
local char, fg, bg = gpu.get(x, y) -- thx wah
ocelot.log(char) local renderBuffer = nil
char = string.byte(char) -- convert from char to char code
ocelot.log(tostring(char)) -- braille rendering
if char < 0x2800 or char > 0x28ff then -- check if char is not a braille character
char = 0 -- yes function raster.units.charToBraille(x,y)
return x*2,y*4
end
function raster.units.brailleToChar(x,y)
return math.ceil(x/2),math.ceil(y/4)
end
function raster.init(width, height, bgcolor)
-- NOTE: Width and height are in characters, not pixels in braille.
-- If the width and height are nil, the entire screen will be used.
if width==nil and height==nil then
width, height = gpu.getResolution()
end end
ocelot.log(tostring(char))
local newi = (x%2)+(y%4)*2 -- original unmodified location in the char for i = 1, width*height do
if x%2==1 and (newi>1 and newi<6) then -- modify it chunksAffected[i] = true
newi = newi+1 -- trust me bro this works end
elseif x%2==0 and (newi>1 and newi<6) then raster.charWidth = width
newi = newi-1 raster.charHeight = height
width, height = raster.units.charToBraille(width, height)
bgcolor = bgcolor or raster.defaultBackgroundColor;
for i = 1, width*height do
display[i] = bgcolor
end
raster.displayWidth = width
raster.displayHeight = height
pcall(function()
renderBuffer = gpu.allocateBuffer()
end)
end
function raster.set(x, y, color)
if x<1 or x>raster.displayWidth or y<1 or y>raster.displayHeight then
return false
end
color = color or raster.defaultForegroundColor
local i = x+y*raster.displayWidth
display[i] = color
local ci = math.floor((x-1)/2)+math.floor((y-1)/4)*raster.charWidth+1
-- ocelot.log(x..","..y..":"..ci)
chunksAffected[ci] = true
return true
end
function raster.get(x, y)
local i = x+y*raster.displayWidth
return display[i] or 0
end
local function stats(arr)
local out = {}
for i=1,#arr do
local v = arr[i]
if out[v]==nil then
out[v]=1
else
out[v] = out[v] + 1
end
end
return out
end
local function getKeys(t)
local keys = {}
for key, _ in pairs(t) do
table.insert(keys, key)
end
return keys
end
local function colorDifference(a,b)
return ((a>>16)&255)-((b>>16)&255)+((a>>8)&255)-((b>>8)&255)+(a&255)-(b&255)
end
local function limitTwoColors(arr)
local colors = getKeys(stats(arr))
for i=1,#arr do
local v=arr[i]
if v==colors[1] then
arr[i]=0
goto continue
elseif v==colors[2] then
arr[i]=1
goto continue
else
--error("Pixel is not in the two colors (raster.lua:90)")
-- get closest color so atleast it kinda shows
if colorDifference(v,colors[1])<colorDifference(v,colors[2]) then
arr[i]=0
else
arr[i]=1
end
end
::continue::
end
return arr,colors[1] or 0,colors[2] or 0
end
local function arrayToBraille(arr)
local codePoint = 0x2800
for i=1,8 do
codePoint = codePoint | arr[i]<<(i-1)
end
return utf8.char(codePoint)
end
function raster.update()
if renderBuffer~=nil then
gpu.setActiveBuffer(renderBuffer)
end
for y=1,raster.displayHeight,4 do
-- gpu.set(0,0,tostring(y))
for x=1,raster.displayWidth,2 do
local ci = math.floor(x/2)+math.floor(y/4)*raster.charWidth+1
if chunksAffected[ci] then
local chunk = {
raster.get(x,y),
raster.get(x,y+1),
raster.get(x,y+2),
raster.get(x+1,y),
raster.get(x+1,y+1),
raster.get(x+1,y+2),
raster.get(x,y+3),
raster.get(x+1,y+3)
}
local colorA = nil
local colorB = nil
chunk,colorA,colorB = limitTwoColors(chunk)
-- print(tostring(colorA)..","..tostring(colorB))
cx,cy=raster.units.brailleToChar(x,y)
gpu.setBackground(colorA)
gpu.setForeground(colorB)
-- gpu.set(cx,cy,tostring(colorB/0xFFFFFF))
gpu.set(cx,cy,arrayToBraille(chunk))
chunksAffected[ci] = false
end
end
end
if renderBuffer~=nil then
gpu.bitblt()
gpu.setActiveBuffer(0)
end
end
function raster.free()
if renderBuffer==nil then
return true
else
return gpu.freeBuffer(renderBuffer)
end
end
-- advanced rendering
function raster.drawLine(x1, y1, x2, y2, color)
x1, y1, x2, y2 = math.floor(x1), math.floor(y1), math.floor(x2), math.floor(y2)
local dx = math.abs(x2 - x1)
local dy = math.abs(y2 - y1)
local sx = x1 < x2 and 1 or -1
local sy = y1 < y2 and 1 or -1
local err = dx - dy
while true do
raster.set(x1, y1, color)
if x1 == x2 and y1 == y2 then
break
end
local e2 = 2 * err
if e2 > -dy then
err = err - dy
x1 = x1 + sx
end
if e2 < dx then
err = err + dx
y1 = y1 + sy
end
end
end
function raster.drawRect(x1,y1,x2,y2,col)
x1, y1, x2, y2 = math.floor(x1), math.floor(y1), math.floor(x2), math.floor(y2)
if x1 > x2 then x1, x2 = x2, x1 end
if y1 > y2 then y1, y2 = y2, y1 end
for x=x1,x2 do
raster.set(x,y1,col)
raster.set(x,y2,col)
end
for y=y1+1,y2-1 do
raster.set(x1,y,col)
raster.set(x2,y,col)
end
end
function raster.fillRect(x1,y1,x2,y2,col)
x1, y1, x2, y2 = math.floor(x1), math.floor(y1), math.floor(x2), math.floor(y2)
if x1 > x2 then x1, x2 = x2, x1 end
if y1 > y2 then y1, y2 = y2, y1 end
for x=x1,x2 do
for y=y1,y2 do
raster.set(x,y,col)
end
end
end
function raster.drawCircle(xc, yc, radius, color)
xc=math.floor(xc)
yc=math.floor(yc)
radius=math.floor(radius)
local x = 0
local y = radius
local d = 3 - 2 * radius
while y >= x do
-- Draw 8 symmetric points
raster.set(xc + x, yc + y, color)
raster.set(xc - x, yc + y, color)
raster.set(xc + x, yc - y, color)
raster.set(xc - x, yc - y, color)
raster.set(xc + y, yc + x, color)
raster.set(xc - y, yc + x, color)
raster.set(xc + y, yc - x, color)
raster.set(xc - y, yc - x, color)
if d < 0 then
d = d + 4 * x + 6
else
d = d + 4 * (x - y) + 10
y = y - 1
end
x = x + 1
end
end
function raster.drawEllipse(x1, y1, x2, y2, color)
if x1 > x2 then x1, x2 = x2, x1 end
if y1 > y2 then y1, y2 = y2, y1 end
local xc = math.floor((x1 + x2) / 2)
local yc = math.floor((y1 + y2) / 2)
local a = math.floor((x2 - x1) / 2)
local b = math.floor((y2 - y1) / 2)
if a <= 0 or b <= 0 then
return
end
if a == b then
raster.drawCircle(xc, yc, a, color)
return
end
if a <= 1 and b <= 1 then
raster.set(xc, yc, color)
return
elseif a <= 1 then
for y = yc - b, yc + b do
raster.set(xc, y, color)
end
return
elseif b <= 1 then
for x = xc - a, xc + a do
raster.set(x, yc, color)
end
return
end
local x = 0
local y = b
local a2 = a * a
local b2 = b * b
local d1 = b2 - (a2 * b) + (0.25 * a2)
local dx = 2 * b2 * x
local dy = 2 * a2 * y
while dx < dy do
raster.set(xc + x, yc + y, color)
raster.set(xc - x, yc + y, color)
raster.set(xc + x, yc - y, color)
raster.set(xc - x, yc - y, color)
if d1 < 0 then
x = x + 1
dx = dx + (2 * b2)
d1 = d1 + dx + b2
else
x = x + 1
y = y - 1
dx = dx + (2 * b2)
dy = dy - (2 * a2)
d1 = d1 + dx - dy + b2
end
end
local d2 = b2 * (x + 0.5) * (x + 0.5) + a2 * (y - 1) * (y - 1) - a2 * b2
while y >= 0 do
raster.set(xc + x, yc + y, color)
raster.set(xc - x, yc + y, color)
raster.set(xc + x, yc - y, color)
raster.set(xc - x, yc - y, color)
if d2 > 0 then
y = y - 1
dy = dy - (2 * a2)
d2 = d2 - dy + a2
else
y = y - 1
x = x + 1
dx = dx + (2 * b2)
dy = dy - (2 * a2)
d2 = d2 + dx - dy + a2
end
end
end
function raster.fillCircle(x, y, r, color)
x, y = math.floor(x + 0.5), math.floor(y + 0.5)
r = math.floor(r + 0.5)
if r <= 0 then return end
local minX, maxX = x - r, x + r
local minY, maxY = y - r, y + r
for py = minY, maxY do
for px = minX, maxX do
local dx, dy = px - x, py - y
local distSquared = dx*dx + dy*dy
if distSquared <= r*r then
raster.set(px, py, color)
end
end
end
end
function raster.fillEllipse(x1, y1, x2, y2, color)
local centerX = (x1 + x2) / 2
local centerY = (y1 + y2) / 2
local a = math.abs(x2 - x1) / 2
local b = math.abs(y2 - y1) / 2
centerX = math.floor(centerX + 0.5)
centerY = math.floor(centerY + 0.5)
a = math.floor(a + 0.5)
b = math.floor(b + 0.5)
if a <= 0 or b <= 0 then return end
if a == b then
raster.fillCircle(centerX, centerY, a, color)
return
end
local minX = centerX - a
local maxX = centerX + a
local minY = centerY - b
local maxY = centerY + b
for y = minY, maxY do
for x = minX, maxX do
local dx = x - centerX
local dy = y - centerY
local value = (dx*dx)/(a*a) + (dy*dy)/(b*b)
if value <= 1 then
raster.set(x, y, color)
end
end
end end
ocelot.log(tostring(newi))
newchar = char|(1<<newi) -- boom and its combined
if newchar < 0x2800 then newchar = newchar+0x2800 end
ocelot.log(tostring(newchar))
termlib.cursorPosX = math.floor(x/2) -- math.floor() for good measure
termlib.cursorPosY = math.floor(y/4)
if newbg == nil then gpu.setBackground(bg) else gpu.setBackground(newbg) end
if newfg == nil then gpu.setForeground(fg) else gpu.setForeground(newfg) end
print(utf8.char(newchar), false, false)
-- print it without newline or wrapping
-- (in case someone wants to draw a pixel off-screen. why would you do that?)
end end
return raster return raster