可以使用的版本

This commit is contained in:
2026-09-20 00:29:42 +08:00
parent f7898adc5d
commit d6969177ed
6 changed files with 73 additions and 25 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
*.log
# Local History for Visual Studio Code
.history/
*.app
+1 -2
View File
@@ -1,5 +1,4 @@
{
"python-envs.defaultEnvManager": "ms-python.python:conda",
"python-envs.defaultPackageManager": "ms-python.python:conda",
"python-envs.pythonProjects": []
"python-envs.defaultPackageManager": "ms-python.python:conda"
}
Binary file not shown.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

+35 -9
View File
@@ -267,18 +267,39 @@ def mono_to_png(data: bytes, width: int, height: int) -> Optional[bytes]:
height=height,
len_=len(data),
)
img = Image.new("1", (width, height)) # 创建 1bpp 黑白图像
pix = img.load() # 获取像素访问对象
'''
expected_bits = (width * height + 7) // 8 # 1bpp 打包长度
expected_bytes = width * height # 每像素 1 字节(0/1或0-255)长度
if len(data) == expected_bits:
# 1bpp:每字节 8 像素,高位在前
logger.debug("[mono_to_png] 使用 1bpp 打包解码")
img = Image.new("1", (width, height))
pix = img.load()
for y in range(height):
row_base = y * width
for x in range(width):
bi = (y * width + x) // 8 # 字节索引:每 8 像素一字节
bit = 7 - (x % 8) # 位索引:高位在前
pix[x, y] = 0 if (bi < len(data) and (data[bi] >> bit) & 1) else 255 # 1→白 0→黑
'''
byte_i = (row_base + x) // 8
bit_i = 7 - (x % 8)
bit = (data[byte_i] >> bit_i) & 1
pix[x, y] = 255 if bit else 0
elif len(data) == expected_bytes:
# 8bit:每字节直接对应一个像素,非零当作白
logger.debug("[mono_to_png] 使用每像素 1 字节解码")
img = Image.new("1", (width, height))
pix = img.load()
for y in range(height):
row_base = y * width
for x in range(width):
pix[x, y] = data[y * width + x]
pix[x, y] = 255 if data[row_base + x] else 0
else:
logger.warning(
"[mono_to_png] payload 长度不匹配,丢弃该帧: data_len={len_}, expected_bits={bits}, expected_bytes={bytes}",
len_=len(data),
bits=expected_bits,
bytes=expected_bytes,
)
return None
buf = io.BytesIO() # 内存缓冲区
img.save(buf, format="PNG") # 保存为 PNG 格式
png_bytes = buf.getvalue()
@@ -329,7 +350,7 @@ def _screen_refresh_loop():
# 当前协议未返回宽高,这里固定为 160x160,可根据实际情况调整
w, h = 160, 160
png = mono_to_png(data, w, h) # 转为 PNG
if png:
if png and isinstance(png, (bytes, bytearray)) and png.startswith(b"\x89PNG\r\n\x1a\n"):
b64 = base64.b64encode(png).decode("ascii") # base64 编码便于 JSON 传输
for sid in list(_screen_clients): # 复制列表避免迭代时修改
try:
@@ -344,6 +365,11 @@ def _screen_refresh_loop():
sid=sid,
err=e,
)
elif png:
logger.warning(
"[_screen_refresh_loop] 生成的 PNG 无效或签名错误,丢弃该帧 png_len={len_}",
len_=len(png),
)
else:
# 拉取失败,累计连续失败次数
consecutive_failures += 1
+33 -10
View File
@@ -392,8 +392,12 @@
}
});
// 服务端推送屏幕:data.png 为 base64 编码的 PNG,解码后绘制到 canvas
const scale = 2; // 放大倍数(160x160 -> 320x320)
let canvasInitialized = false;
let latestFrameId = 0; // 用于丢弃旧帧,避免异步解码乱序导致闪烁
socket.on('screen', (data) => {
if (!data.png) return;
const frameId = ++latestFrameId;
// 无 base64 数据则跳过
const binary = atob(data.png);
// base64 解码为二进制字符串
@@ -406,21 +410,38 @@
// 创建临时 URL
const img = new Image();
img.onload = () => {
// 图片加载完成后,放大一倍显示(160x160 -> 320x320)
const scale = 2;
canvas.width = img.width * scale;
canvas.height = img.height * scale;
const ctx = canvas.getContext('2d');
ctx.imageSmoothingEnabled = false;
// 关闭平滑,像素清晰放大
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, canvas.width, canvas.height);
// 绘制到 canvas(2 倍尺寸)
// 图片加载完成后:若该帧不是最新帧则直接丢弃
if (frameId !== latestFrameId) {
URL.revokeObjectURL(url);
return;
}
// 首帧初始化 canvas 尺寸(只做一次,避免反复重设导致清空闪烁)
const targetW = img.width * scale;
const targetH = img.height * scale;
if (!canvasInitialized) {
canvas.width = targetW;
canvas.height = targetH;
canvasInitialized = true;
canvas.style.display = 'block';
placeholder.style.display = 'none';
// 显示 canvas,隐藏占位符
}
const ctx = canvas.getContext('2d');
ctx.imageSmoothingEnabled = false;
// 关闭平滑:像素清晰放大
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, canvas.width, canvas.height);
URL.revokeObjectURL(url);
// 释放临时 URL
};
img.onerror = () => {
// 发生错误时同样释放临时 URL
if (frameId === latestFrameId) {
// 不弹窗,避免影响观察;必要时可在这里加日志
}
URL.revokeObjectURL(url);
};
img.src = url;
// 触发加载
});
@@ -465,6 +486,8 @@
placeholder.style.display = '';
placeholder.textContent = '点击「连接」连接装置';
// 隐藏 canvas,显示占位符
canvasInitialized = false;
latestFrameId = 0;
}
document.querySelectorAll('.dpad .btn, .action-btn').forEach(btn => {