jim800121chen c54f16fca0 Initial commit: visionA monorepo with local-tool subproject
local-tool/: visionA-local desktop app
- M1: Wails shell + Go server + Next.js UI + Mock mode (macOS dmg ready)
- M2: i18n (zh-TW/en) + Settings 4-tab refactor
- M3: Embedded Python 3.12 runtime (python-build-standalone) + KneronPLUS wheels
- M4: Windows Inno Setup script (build on Windows runner)
- M5: Linux AppImage script + udev rule (build on Linux runner)
- M6: ffmpeg (GPL, pending legal review) + yt-dlp bundled
- Lifecycle: watchServer health check, fatal native dialog,
            Wails IPC raise endpoint, stale process cleanup

.autoflow/: full PRD / Design Spec / Architecture / Testing docs
            (4 rounds tri-party discussion + cross review)
.github/workflows/: macOS / Windows / Linux build CI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 22:10:38 +08:00

96 lines
2.4 KiB
Go

package camera
import (
"bytes"
"fmt"
"image"
"image/color"
"image/jpeg"
"time"
)
type MockCamera struct {
width int
height int
frameCount int
}
func NewMockCamera(width, height int) *MockCamera {
return &MockCamera{width: width, height: height}
}
func (mc *MockCamera) ReadFrame() ([]byte, error) {
mc.frameCount++
return mc.generateTestCard()
}
func (mc *MockCamera) generateTestCard() ([]byte, error) {
img := image.NewRGBA(image.Rect(0, 0, mc.width, mc.height))
offset := mc.frameCount % mc.width
for y := 0; y < mc.height; y++ {
for x := 0; x < mc.width; x++ {
pos := (x + offset) % mc.width
ratio := float64(pos) / float64(mc.width)
var r, g, b uint8
if ratio < 0.33 {
r = uint8(255 * (1 - ratio/0.33))
g = uint8(255 * ratio / 0.33)
} else if ratio < 0.66 {
g = uint8(255 * (1 - (ratio-0.33)/0.33))
b = uint8(255 * (ratio - 0.33) / 0.33)
} else {
b = uint8(255 * (1 - (ratio-0.66)/0.34))
r = uint8(255 * (ratio - 0.66) / 0.34)
}
img.SetRGBA(x, y, color.RGBA{R: r, G: g, B: b, A: 255})
}
}
// Draw dark overlay bar at top for text area
for y := 0; y < 40; y++ {
for x := 0; x < mc.width; x++ {
img.SetRGBA(x, y, color.RGBA{R: 0, G: 0, B: 0, A: 180})
}
}
// Draw "MOCK CAMERA" text block and frame counter using simple rectangles
drawTextBlock(img, 10, 10, fmt.Sprintf("MOCK CAMERA | Frame: %d | %s", mc.frameCount, time.Now().Format("15:04:05")))
// Draw center crosshair
cx, cy := mc.width/2, mc.height/2
for i := -20; i <= 20; i++ {
if cx+i >= 0 && cx+i < mc.width {
img.SetRGBA(cx+i, cy, color.RGBA{R: 255, G: 255, B: 255, A: 200})
}
if cy+i >= 0 && cy+i < mc.height {
img.SetRGBA(cx, cy+i, color.RGBA{R: 255, G: 255, B: 255, A: 200})
}
}
var buf bytes.Buffer
if err := jpeg.Encode(&buf, img, &jpeg.Options{Quality: 75}); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func drawTextBlock(img *image.RGBA, x, y int, text string) {
white := color.RGBA{R: 255, G: 255, B: 255, A: 255}
// Simple pixel-based text rendering: each character is a 5x7 block
for i, ch := range text {
if ch == ' ' {
continue
}
px := x + i*6
// Draw a small white dot for each character position
for dy := 0; dy < 5; dy++ {
for dx := 0; dx < 4; dx++ {
if px+dx < img.Bounds().Max.X && y+dy < img.Bounds().Max.Y {
img.SetRGBA(px+dx, y+dy, white)
}
}
}
}
}