可以使用的版本
This commit is contained in:
+1
-1
@@ -5,7 +5,7 @@
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
*.log
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
*.app
|
||||
|
||||
Vendored
+1
-2
@@ -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.
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
+35
-9
@@ -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
@@ -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 => {
|
||||
|
||||
Reference in New Issue
Block a user