Temple OS源码研究
博客:源码解读
TOS源码: https://github.com/cia-foundation/TempleOS
启动过程: https://minexew.github.io/2020/02/27/templeos-loader-part1.html
建设性看法:http://www.codersnotes.com/notes/a-constructive-look-at-templeos/
移植文字游戏:https://www.jwhitham.org/2015/07/porting-third-party-programs-to-templeos.html
常用命令
TaskRep; //任务管理器 Kill(0x00100300); //杀死进程 Unzip("test.HC.Z"); Type("StadiumBG.GR.Z"); //打印DolDoc(文档 或 精灵Spirit) Reboot; #include "test.HC" //立即执行
Ctrl+F1 快速输入/召唤自动补全
Ctrl+Alt+X 类似linux下的Ctrl+C,中断当前任务
Ctrl+R 创建/编辑spirit
Ctrl+T 显示doldoc为raw text
图片嵌入
详见~/Demo/Games/Stadium/
: https://github.com/cia-foundation/TempleOS/tree/archive/Demo/Games/Stadium
找demo
看注释,说是.DD
生成了.GR
文件。我们可以写一个python,来将任意图片,转换成.DD
文件。
DolDoc
打开StadiumBG.DD
,文件头部有:
$SP,"<01>",BI=1$
$
:类似Minecraft的§
,表示非普通文本,不过要头尾都有。
SP
: Spirit精灵
"<01>"
: 占位符,留空则不显示。类似markdown的
BI
: 类似唯一id。每创建一个新spirit时+1。
.GR
后面跟着二进制数据,结合GR文件结构: 一个字节,但最多16种颜色。0x00~0x0F,透明为0xFF
首先Ctrl+R,得到这是一个BitMap,长640,高472
(472)₁₀=(1D8)₁₆,发现是little endian方式存储(x86)。有点反人类,但对机器友好。
得到结构:
"$SP,"",BI=1$"; //DD文件,前面全文字,后面全bin //BI头: 0x00; 0x0100 0000 0000 0000; //BI值:0x0200 0000 0000 0000 0x129c 0400; //BI内所有元素的字节长(方便访问下一个BI) 0x0100 0000; //固定,但可能是某个会递增的id? //同一BI内,元素头: 0x17; //固定,代表该元素为bitmap I32 offset_width; I32 offset_height; U32 width; //建议为8的倍数 U32 height; U8 body[640*472]={ 0x0F,0x0F,...,0x0F, //每行640个0x0F,默认为白色0x0F // 0xFF... //每行尾随补充一定数量的0xFF,使得一行字节数能**凑齐** 8的倍数。如一行640,则无须补充0xFF;若一行14,则还需补充2个0xFF,凑到16个字节每行 }[472]; //共有472行 0x00; //每个**元素**的EOF,body最后一行最后一字节置为0x00,后延续一次每行的0xFF数量。 0x00; //文件末尾的EOF。若为最后一个body,则body的最后一行的最后一字节不必为0xFF。如0x0f 0x0f (0xFF...) 0x00 EOF
py
#!/bin/env python import struct import cv2 import numpy as np import taichi as ti from typing import Literal from PIL import Image import os import platform OS = platform.system() DEBUG=True WIDTH=640 HEIGHT=480 MAX=255 BAYER_M=(4,1) # 4x4 bayer matrix, scale by 1 DD_SAVE='/home/n/document/code/templeos-plus/o.DD' TMP_PNG='tmp_dither_{}.PNG' TMP_DIR={ 'Linux':'/tmp/', 'Windows':'C:/Users/lenovo/AppData/Local/Temp/', } dither_algorithm:Literal['bayer','floyd']='floyd' interpolation=cv2.INTER_LANCZOS4 """ cv2.INTER_NEAREST :最近邻插值。最快,但通常效果最差 cv2.INTER_LINEAR :双线性插值。默认,适用于大多数情况,平衡了速度和图像质量。 cv2.INTER_CUBIC :三次样条插值。计算复杂度较高,但图像质量较好,适用于对图像质量要求较高的场景。 cv2.INTER_AREA :区域插值。适用于缩小图像,可以减少锯齿效应。 cv2.INTER_LANCZOS4 :Lanczos 插值。使用 8x8 邻域进行插值,计算复杂度最高,但图像质量最好,适用于对图像质量要求极高的场景。 """ BIS = [ { 'text': '<1>', 'elem': [ { 'img': './input.jpg', 'w': 640, }, { 'img': './transparent.png', 'h': 640, 'offset_w': -320, }, ] }, { 'text': '<2>', 'elem': [ { 'img': './input.jpg', 'w': 640, # 同时指定h,w时,强制拉伸 'h': 640, }, { 'img': './transparent.png', 'offset_h': -320, }, ] }, ] cmd = f'\\rm {TMP_DIR[OS]}{TMP_PNG.format('*')}' # cmd = f'\\rm {TMP_DIR[OS]}tmp*.PNG' print('cmd',cmd) os.system(cmd) if OS == 'Linux' else None # Dont't modify below 不要修改以下内容 TEXT_SP='$SP,"{}",BI={}$' HEAD_BI=''' 00 {} 0000 0000 {} 0100 0000 ''' TRANSPARENT = 0xFF PALETTE = [ 0x000000, 0x0000AA, 0x00AA00, 0x00AAAA, 0xAA0000, 0xAA00AA, 0xAA5500, 0xAAAAAA, 0x555555, 0x5555FF, 0x55FF55, 0x55FFFF, 0xFF5555, 0xFF55FF, 0xFFFF55, 0xFFFFFF ] PALETTE = [(c & 0xFF, c >> 8 & 0xFF, c>>16 & 0xFF) for c in PALETTE] #BGR for opencv default color space # print('🎨 palette',palette) if DEBUG else None CHANNEL = len(PALETTE[0]) PALETTE_SET = [ sorted(list(set(c[ch] for c in PALETTE))) for ch in range(CHANNEL)] PALETTE_SET = [ [0] if ch[0]!=0 else [] + ch + [0xFFFFFF] for ch in PALETTE_SET] print('PALETTE_SET',PALETTE_SET) if DEBUG else None def show_image(img,lib:Literal['cv2','pil','sys']='pil'): """ - cv2: 无法显示alpha透明通道,但不会生成临时文件 - pil: 可以显示alpha通道。在Linux下由于调用xdg-open,会生成临时文件 """ if lib == 'sys': if OS == 'Linux': os.environ["QT_QPA_PLATFORM"] = "dxcb" os.system(f'xdg-open "{img}"') elif OS == 'Windows': os.system(f'start "{img}"') return if isinstance(img, str): img = cv2.imread(img) elif img.dtype != 'uint8': img = np.clip(img, 0, MAX) # 将图片像素值限制在 0~255 之间 img = img.astype(np.uint8) if lib == 'cv2': os.environ.pop("QT_QPA_PLATFORM", None) if OS == 'Linux' else None cv2.imshow('Image', img) while True: key = cv2.waitKey(1) & 0xFF if key == 27 or key == 32: # ESC或空格 break cv2.destroyAllWindows() elif lib == 'pil': os.environ["QT_QPA_PLATFORM"] = "dxcb" if OS == 'Linux' else None # 根据不同操作系统,灵活调整 img = cv2.cvtColor(img, cv2.COLOR_BGRA2RGBA) pil_img = Image.fromarray(img) pil_img.show() def get_fit_hw(img,hw) -> tuple[int,int]: h,w = hw old_h, old_w = img.shape[:2] if h and w: # 强制拉伸,局部dic['h'] > 全局HEIGHT return h, w elif w: new_w = w new_h = int(new_w * old_h / old_w) elif h: new_h = h new_w = int(new_h * old_w / old_h) elif WIDTH>0 and HEIGHT>0: return WIDTH,HEIGHT elif WIDTH>0: new_w = WIDTH new_h = int(new_w * old_h / old_w) elif HEIGHT>0: new_h = HEIGHT new_w = int(new_h * old_w / old_h) return new_h, new_w def hsv(img, h=0, s=1, v=1): """调整图像的色调、饱和度、亮度""" # channel = img.shape[2] # if channel == 4: # alpha = img[:, :, 3] # img = cv2.cvtColor(img, cv2.COLOR_BGRA2BGR) img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) img[:, :, 0] = np.clip((img[:, :, 0] + h)%MAX, 0, 255) img[:, :, 1] = np.clip(img[:, :, 1] * s, 0, 255) img[:, :, 2] = np.clip(img[:, :, 2] * v, 0, 255) img = cv2.cvtColor(img, cv2.COLOR_HSV2BGR) # if channel == 4: # img = np.dstack((img, alpha)) return img def bayer_matrix(n: int = 4): """ 生成任意大小的bayer矩阵 但其实,n=2,3,4,8 为什么8是最大值?因为256=2^8,所以8x8的bayer矩阵可以覆盖256色 """ M2 = np.array([[0, 2], [3, 1]], dtype=np.uint8) if n == 3: return [[0, 7, 3], [6, 5, 2], [4, 1, 8]] elif np.log2(n) % 1 != 0: # n不是2的幂次方,%1取小数部分 raise ValueError('n must be a power of 2') elif n == 2: return M2 else: half = n // 2 M = np.zeros((n, n), dtype=np.uint8) M[:half, :half] = bayer_matrix(half) * 4 M[:half, half:] = bayer_matrix(half) * 4 + 2 * np.eye(half) M[half:, :half] = bayer_matrix(half) * 4 + 3 * np.eye(half) M[half:, half:] = bayer_matrix(half) * 4 + np.eye(half) return M def gen_bayerM(n=4,scale=1): bayerM = ti.ndarray(shape=(n*scale, n*scale), dtype=ti.u8) bayerRaw = np.matrix(bayer_matrix(n), dtype=np.uint8) bayerRaw = np.kron(bayerRaw, np.ones((scale,scale), dtype=np.uint8)) print(bayerRaw,'← bayerM') if DEBUG else None bayerM.from_numpy(bayerRaw) return bayerM type_2d_vec = ti.types.ndarray(element_dim=1,ndim=2) type_2d = ti.types.ndarray(element_dim=0,ndim=2) # type_2d_vec = object # type_2d = object @ti.func def get_closest_bgr(old_pixel): min_distance = 0xFFFFFFF new_pixel = ti.Vector([0,0,0],dt=ti.i32) index = 0 i = 0 r, g, b = old_pixel for c in ti.static(PALETTE): cr, cg, cb = c distance = ti.int32((r - cr) ** 2 + (g - cg) ** 2 + (b - cb) ** 2) if distance < min_distance: min_distance = distance new_pixel = c index = i i+=1 return [new_pixel, index] @ti.kernel def dither_bayer(img:type_2d_vec, bayerM:type_2d,hex:ti.template(),alpha:type_2d): h = img.shape[0] w = img.shape[1] n = bayerM.shape[0] has_alpha = alpha.shape[0] > 1 # print('has_alpha',has_alpha) if DEBUG else None for i in range(h): for j in range(w): th = bayerM[i % n, j % n] / (n**2) for ch in ti.static(range(CHANNEL)): for b in ti.static(range(len(PALETTE_SET[ch])-1)): if PALETTE_SET[ch][b] < img[i, j][ch]/th < PALETTE_SET[ch][b+1]: img[i, j][ch] = PALETTE_SET[ch][b] img[i, j],index = get_closest_bgr(img[i,j]) if has_alpha: if alpha[i, j] > th * MAX: alpha[i, j] = MAX # 透明度大于阈值,设为不透明 hex[i, j] = index else: alpha[i, j] = 0 hex[i, j] = TRANSPARENT else: hex[i, j] = index # fix taichi h = img.shape[0] w = img.shape[1] for i in range(h): if i%512==0: # taichi cpu fix for j in range(w): img[i, j],index = get_closest_bgr(img[i, j]) @ti.kernel def dither_floyd(img:type_2d_vec,hex:ti.template(),alpha:type_2d): h = img.shape[0] w = img.shape[1] has_alpha = alpha.shape[0] > 1 # print('has_alpha',has_alpha) if DEBUG else None for i in range(h): for j in range(w): oldpixel = img[i, j] oldpixel = ti.math.clamp(img[i, j],0,MAX) newpixel,index = get_closest_bgr(oldpixel) quant_error = oldpixel - newpixel img[i, j] = newpixel if j+1<w: img[i, j + 1] += quant_error * 7 // 16 if i+1<h: if j-1>=0: img[i + 1, j - 1] += quant_error * 3 // 16 img[i + 1, j] += quant_error * 5 // 16 if j+1<w: img[i + 1, j + 1] += quant_error // 16 if has_alpha: old_a = alpha[i, j] if alpha[i, j] * 2 > MAX: alpha[i, j] = MAX hex[i, j] = index else: alpha[i, j] = 0 hex[i, j] = TRANSPARENT alpha_error = old_a - alpha[i, j] if j+1<w: alpha[i, j + 1] += alpha_error * 7 // 16 if i+1<h: if j-1>=0: alpha[i + 1, j - 1] += alpha_error * 3 // 16 alpha[i + 1, j] += alpha_error * 5 // 16 if j+1<w: alpha[i + 1, j + 1] += alpha_error // 16 else: hex[i, j] = index # fix taichi h = img.shape[0] w = img.shape[1] for i in range(h): if i%512==0: # taichi cpu fix for j in range(w): img[i, j],index = get_closest_bgr(img[i, j]) hex[i, j] = index def dither_main(img,is_last_one:False): h,w = img.shape[:2] hex = bytearray() FF_count = 8 - w%8 if w%8 else 0 FF = b'\xFF' * FF_count hex2d = ti.field(dtype=ti.u8,shape=(h,w)) alpha=np.zeros((1,1),dtype=np.int32) if img.shape[2] == 4: # 有alpha通道 alpha = img[:,:,3] alpha = alpha.astype(np.int32) img = cv2.cvtColor(img, cv2.COLOR_BGRA2BGR) if dither_algorithm == 'bayer': img = hsv(img, s=1, v=.3) # 💡 这里默认调低了亮度,可以根据实际情况调整 img = img.astype(np.int32) alpha = np.ascontiguousarray(alpha,dtype=np.int32) if dither_algorithm == 'floyd': dither_floyd(img,hex2d,alpha) elif dither_algorithm == 'bayer': dither_bayer(img, gen_bayerM(*BAYER_M), hex2d, alpha) hex2d = hex2d.to_numpy() # 将img与alpha合并 if alpha.any(): img = np.dstack((img, alpha)) img = img.astype(np.uint8) for y in range(h): for x in range(w): hex.extend(struct.pack('<B', hex2d[y, x])) hex.extend(FF) if not is_last_one: hex[-FF_count-1] = 0x00 return img,bytes(hex) def get_bitmap(elem,is_last_one=False) -> bytes: BITMAP = struct.pack('<B',0x17) bi = b'' for dic in elem: img_path = dic['img'] old_hw = [dic.get('h', None),dic.get('w', None)] offset_wh = (dic.get('offset_w', 0),dic.get('offset_h', 0)) wh = struct.pack('<i',offset_wh[0]) wh += struct.pack('<i',offset_wh[1]) img = cv2.imread(img_path,cv2.IMREAD_UNCHANGED) if img is None: raise Exception('Image not found: ' + img_path) print('raw_size',img.shape) if DEBUG else None new_h,new_w = get_fit_hw(img,old_hw) img = cv2.resize(img, (new_w,new_h), interpolation=interpolation) wh += struct.pack('<I',new_w) wh += struct.pack('<I',new_h) print('fitted_size',img.shape) if DEBUG else None # print('hex_wh',struct.pack('<H',new_w),struct.pack('<H',new_h)) img,body = dither_main(img,is_last_one) if DEBUG: if OS == 'Windows': show_image(img,'cv2') else: save_path = TMP_DIR[OS] + TMP_PNG.format(os.path.basename(img_path)) print('save_path:',save_path) cv2.imwrite(save_path,img) print('bitmap head:',(BITMAP + wh).hex(' ',2)) if DEBUG else None bi += BITMAP + wh + body return bi def get_BIs(bis) -> bytes: hex = b'' for i in range(len(bis)): hex+=TEXT_SP.format(bis[i]['text'],i+1).encode() for i in range(len(bis)): id = i+1 bmap=get_bitmap(bis[i]['elem'], id == len(bis)) hex_len=struct.pack('<I',len(bmap)+1).hex(' ',2) # +1 id_bi = struct.pack('<I',id).hex(' ',2) head_bi=bytes.fromhex(HEAD_BI.format(id_bi,hex_len).strip()) # BI_ID's max is unsigned short = 2^16 print('BI head:',HEAD_BI.format(id_bi,hex_len)) if DEBUG else None hex+=head_bi hex+=bmap return hex + b'\x00' ti.init(arch=ti.cpu,debug=False) hex=get_BIs(BIS) with open(DD_SAVE, 'wb') as f: f.write(hex) if OS != 'Windows': pngs = os.popen(f'\\ls {TMP_DIR[OS]}{TMP_PNG.format('*')}').read().split('\n') show_image(pngs[0],'sys')
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步