basic graphics stack
everything is defined in graphics.js it is curently very basic et poorly performing but png/jpg files can be displayed
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
//@ts-check
|
||||
const fs = require('fs')
|
||||
const jimp = require('jimp')
|
||||
|
||||
const screen = getScreenDimensions()
|
||||
const buffer = Buffer.alloc(screen.width * screen.height * screen.byteDepth)
|
||||
//TODO: implement automatic cache cleanup
|
||||
/**
|
||||
* @type {{ [key: string]: import('@jimp/core').Bitmap }}
|
||||
*/
|
||||
const imageCache = {}
|
||||
|
||||
function getScreenDimensions() {
|
||||
const vsize = fs.readFileSync('/sys/class/graphics/fb0/virtual_size', { encoding: 'utf-8' }).split(',')
|
||||
const byteDepth = parseInt(fs.readFileSync('/sys/class/graphics/fb0/bits_per_pixel').toString()) / 8
|
||||
|
||||
return {
|
||||
width: parseInt(vsize[0]),
|
||||
height: parseInt(vsize[1]),
|
||||
byteDepth
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param { Buffer } data
|
||||
*/
|
||||
function flip(data = buffer) {
|
||||
try {
|
||||
fs.writeFileSync('/dev/fb0', data, { encoding: 'binary' })
|
||||
} catch(e) {
|
||||
console.log("could not write to /dev/fb0")
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
function clear() {
|
||||
buffer.fill(0)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} filepath
|
||||
*/
|
||||
function removeFromCache(filepath) {
|
||||
delete imageCache[filepath]
|
||||
}
|
||||
|
||||
/**
|
||||
* @param { number } x
|
||||
* @param { number } y
|
||||
* @param { string } filepath
|
||||
* //!\\ THE CACHE DOESN'T CLEAN ITSELF UP //!\\
|
||||
* once you finished displaying a image use removeFromCache to free the associated memory
|
||||
*/
|
||||
async function drawImgFile(x, y, filepath) {
|
||||
let bitmap
|
||||
//not caching would imply reading the image each time we want to write it to the screen even for small movement
|
||||
if(!imageCache[filepath]) {
|
||||
const img = await jimp.read(filepath)
|
||||
bitmap = img.bitmap
|
||||
bitmap.data = rgbaTObgra(bitmap.data)
|
||||
imageCache[filepath] = bitmap
|
||||
} else {
|
||||
bitmap = imageCache[filepath]
|
||||
}
|
||||
|
||||
|
||||
|
||||
for(let dy = 0; dy < bitmap.height; dy++) {
|
||||
let bitmapLineEnd = bitmap.width * screen.byteDepth * (dy + 1)
|
||||
let bitmapLineStart = bitmap.width * screen.byteDepth * dy
|
||||
let dx = x
|
||||
|
||||
const offscreenRight = dx + bitmap.width - screen.width
|
||||
if(offscreenRight > 0) {
|
||||
bitmapLineEnd -= offscreenRight*4
|
||||
}
|
||||
|
||||
const offscreenLeft = -dx
|
||||
if(offscreenLeft > 0) {
|
||||
bitmapLineStart += offscreenLeft*4
|
||||
dx = 0
|
||||
}
|
||||
|
||||
const offscreenTop = -y
|
||||
if(offscreenTop >= dy) {
|
||||
dy = offscreenTop
|
||||
continue
|
||||
}
|
||||
|
||||
const offscreenBottom = bitmap.height + y - screen.height
|
||||
if(offscreenBottom > 0 && bitmap.height - offscreenBottom <= dy) {
|
||||
break
|
||||
}
|
||||
|
||||
if(bitmapLineEnd < bitmapLineStart) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
const line = bitmap.data.subarray(bitmapLineStart, bitmapLineEnd)
|
||||
buffer.set(Array.from(line), dx * screen.byteDepth + screen.width * (dy + y) * screen.byteDepth)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param { Buffer } buff
|
||||
*/
|
||||
function rgbaTObgra(buff) {
|
||||
for(let i=0; i < buff.length; i += 4) {
|
||||
const r = buff.readUInt8(i)
|
||||
const b = buff.readUInt8(i+2)
|
||||
|
||||
buff.set([b], i)
|
||||
buff.set([r], i+2)
|
||||
}
|
||||
return buff
|
||||
}
|
||||
|
||||
/**
|
||||
* @param { string } color
|
||||
* @returns { number[] }
|
||||
*/
|
||||
function parseHexColor(color) {
|
||||
if(!color.startsWith('#') || color.length !== 7) {
|
||||
throw new Error('invalid hex color code')
|
||||
}
|
||||
const r = parseInt(color.substring(1,2), 16)
|
||||
const v = parseInt(color.substring(3,4), 16)
|
||||
const b = parseInt(color.substring(5,6), 16)
|
||||
return [b, v, r, 0]
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param { number } x
|
||||
* @param { number } y
|
||||
* @param { string | number[] } colorCode
|
||||
*/
|
||||
function setPixel(x, y, colorCode) {
|
||||
if (screen.byteDepth !== 4) {
|
||||
throw new Error('Depth not supported yet')
|
||||
}
|
||||
|
||||
if(typeof colorCode === 'string') {
|
||||
colorCode = parseHexColor(colorCode)
|
||||
}
|
||||
buffer.set(colorCode, (x + y * screen.width) * screen.byteDepth)
|
||||
}
|
||||
|
||||
module.exports = { flip, clear, getScreenDimensions, drawImgFile, parseHexColor, setPixel }
|
||||
Reference in New Issue
Block a user