项目 4 任务 2 网络摄像机:PictureBox、Image / Bitmap、照片列表
CI/CD / test (push) Canceled after 0s
CI/CD / deploy (push) Canceled after 0s

- 摄像头:自绘东围墙场景(Camera/scene.svg),按云台位置切成 640×480 帧(build-frames.ts);
  Pan 每次转一格,红外对射被遮挡时画面出现翻墙的人;类名、委托、事件参数与教材 dll 一致,
  ImageRead 每 250 ms 在后台线程触发
- Image / Bitmap 保存真实图片数据:Save、FromFile、FromStream,文件夹不存在时报 GDI+ 一般性错误;
  图片每张只发给浏览器一次(按内容哈希缓存)
- 程序所在目录(AppDomain.CurrentDomain.BaseDirectory,编译检查单独放行)和当前目录指向可写的 bin/Debug,
  沙箱里在沙箱的临时目录
- PictureBox(SizeMode、BorderStyle);设计器工具箱加"图片框";运行区显示程序保存的图片,点开看大图;
  实训台摄像头图标显示开启状态
- 评分规则加图片框显示图片、摄像头开关与转向、保存图片三种检查;示例 p4-t2-网络摄像机
- 测试:摄像头端到端、图片读写与程序目录、AppDomain 编译检查、评分出错情况

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-22 20:58:09 +08:00
1 parent 4ed9450c02
commit 6b088240da
76 files changed
+1290 -14

No files matched your search

@@ -0,0 +1,48 @@
// 生成网络摄像机的画面帧:把 scene.svg 渲染成全景图(有人翻墙 / 没有人两种),按云台位置切成 640×480 的 JPEG。
// 帧文件提交在仓库里,改了 scene.svg 后重新运行:bun runtime/Sim.Devices/Camera/build-frames.ts
// 需要 rsvg-convert(librsvg)和 magick(ImageMagick)。
import { mkdir, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
/** 与 IPCameraHelper.cs 中的常量一致 */
const VIEW = { width: 640, height: 480 }
const STEPS = { x: 7, y: 3 }
const STRIDE = { x: 200, y: 160 }
const dir = import.meta.dir
const framesDir = join(dir, 'frames')
const work = join(tmpdir(), `camera-frames-${process.pid}`)
async function run(cmd: string[]) {
const proc = Bun.spawn(cmd, { stderr: 'inherit' })
if ((await proc.exited) !== 0) throw new Error(`${cmd[0]} 失败`)
}
const scene = await Bun.file(join(dir, 'scene.svg')).text()
const intruder = scene
.replace('id="intruder" display="none"', 'id="intruder"')
.replace('<path id="beam" ', '<path id="beam" display="none" ')
.replace('id="beam-broken" display="none"', 'id="beam-broken"')
if (intruder === scene) throw new Error('scene.svg 里找不到 #intruder')
await rm(framesDir, { recursive: true, force: true })
await mkdir(framesDir, { recursive: true })
await mkdir(work, { recursive: true })
try {
for (const [flag, svg] of [[0, scene], [1, intruder]] as const) {
const svgPath = join(work, `scene-${flag}.svg`)
const pngPath = join(work, `scene-${flag}.png`)
await Bun.write(svgPath, svg)
await run(['rsvg-convert', svgPath, '-o', pngPath])
for (let x = 0; x < STEPS.x; x++) {
for (let y = 0; y < STEPS.y; y++) {
const crop = `${VIEW.width}x${VIEW.height}+${x * STRIDE.x}+${y * STRIDE.y}`
await run(['magick', pngPath, '-crop', crop, '+repage', '-strip', '-quality', '80', join(framesDir, `${x}-${y}-${flag}.jpg`)])
}
}
}
} finally {
await rm(work, { recursive: true, force: true })
}
console.log(`已生成 ${STEPS.x * STEPS.y * 2} 帧:${framesDir}`)