Files
Linux.js---javascript-OS/rootfs overrides/chibrax/graphics.js
T
Marc a6dbee234c basic graphics stack
everything is defined in graphics.js it is curently very basic et poorly performing but png/jpg files can be displayed
2022-02-01 23:24:00 +01:00

150 lines
3.8 KiB
JavaScript

//@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 }