Files
ZXCLI 9db940a615 demo3 迁移到 mag160c_render 抽象库(v6);render 库加手动 FFC/NUC 访问器/钩子
- mag160c_demo3.c 从内联管线重写为 render API 调用:删除 DDT 加载/NUC/
  LUT/gray/2x/FFC 状态机实现(~700 行),保留 USB 取帧/GDI/探针/附加环节
- 附加环节(0x230 帧差/0x41fe8 隔行平滑/0x30 gray 覆盖)经 render 的
  post_nuc/post_gray 钩子挂接,编译开关与运行时按键 0/8/9 不变
- mag160c_render 新增 mag160c_render_request_ffc / get_nuc 访问器
- 实测无回归:FFC 时序 init@20→FFC(1)@28→漂移触发,与 v5 一致;
  stats 正常,auto BMP 生成正常;全开关版本编译通过
- linux_demo 修帧组装(38400px+28B 尾),快门=31492 sel=2 正常
2026-08-19 15:41:34 +08:00

791 lines
30 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* MAG160C Windows Demo v6 - official pipeline via mag160c_render API.
*
* The whole official frame pipeline now lives in the platform-independent
* module csdk/src/mag160c_render.c (shutter extract -> FFC state machine ->
* endpoint select + Q12 interp -> ref 4x mean -> NUC -> blind -> stats/
* window -> LUT1024 -> gray -> 2x -> palette). This app only does:
* USB capture (libusb), the display window (GDI), diagnostics and the
* official "extra paths" (0x230 / 0x41fe8 / 0x30) through the render
* post-processing hooks.
*
* Same pixel behavior as v5 (verified 0/19200 NUC, 0/76800 gray, ghost =
* official frozen-ref artifact). Building/behavior switches:
* -DOFF_IMPL_230 / OFF_IMPL_41FE8 / OFF_IMPL_30 / OFF_IMPL_UPSCALE=1
* keys 0/8/9 toggle the extra paths at runtime.
*/
#define _WIN32_WINNT 0x0601
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <math.h>
#include <libusb.h>
#include "mag160c/mag160c.h"
#include "mag160c/mag160c_render.h"
#include "mag160c/mag160c_display.h"
#include "mag160c_official_palette256.h"
#include "mag160c_official_t2e.h"
#define W 160
#define H 120
#define NPIX (W * H)
#define VW 320
#define VH 240
#define OFF_DDT "mag160c_official.ddt"
/* =====================================================================
* 官方附加环节移植(编译开关,默认 0 = 官方当前会话行为)
* = 1 时对应代码路径可用(运行时控制变量再决定是否执行):
* OFF_IMPL_230 帧差调整 FUN_1800175a0 (官方 0x230=0 关闭)
* OFF_IMPL_41FE8 隔行平滑 FUN_1800184f0/660(官方 0x41fe8=0 关闭)
* OFF_IMPL_30 gray 覆盖路径 0x17770/0x17c40(官方 0x30=0 关闭)
* OFF_IMPL_UPSCALE 多比例升采样 4x/8x/1.5x/3x(当前设备 2x
* ===================================================================== */
#define OFF_IMPL_230 0
#define OFF_IMPL_41FE8 0
#define OFF_IMPL_30 0
#define OFF_IMPL_UPSCALE 0
/* 运行时控制变量(官方会话实测值;编译开关开启时默认展示该路径,
* 可用按键 0/8/9 切换回官方默认) */
static int g_ctrl_230 = OFF_IMPL_230; /* 官方 dev+0x230 */
static int g_ctrl_41fe8 = OFF_IMPL_41FE8 ? 1 : 0; /* 官方 dev+0x41fe8 */
static int g_ctrl_30 = OFF_IMPL_30; /* 官方 dev+0x30 渲染模式 */
static int g_ctrl_84 = 0; /* 官方 dev+0x84 */
static unsigned short *g_buf278; /* 官方 dev+0x278(当前会话 NULL */
static int g_dev24 = 5; /* 0x47944: window base (window scale) */
static int g_dev4c_shift = 3; /* 0x4794c */
static int g_dev47950 = 0; /* 0x47950: 0 static / 1 time-based */
static int g_dev47948 = -50000; /* 0x47948: comp offset */
static int g_im41fec; /* 官方 dev+0x41fec */
static int g_im41ff0; /* 官方 dev+0x41ff0 */
static unsigned short g_sm_buf[((W / 2 + 2) * (H / 2 + 1) + 16) * 2];
static unsigned short g_sm4_buf[((W / 4 + 2) * (H / 4 + 1) + 16) * 2];
static libusb_context *g_ctx;
static libusb_device_handle *g_h;
static unsigned char g_frame[40000];
static unsigned char g_bmp[54 + VW * VH * 3];
static char g_title[256];
static mag160c_render_t *g_render;
static int g_fcount; /* rendered frames */
static int g_global_frames; /* frames since start (incl. FFC window) */
static double g_fps;
static double g_max_temp = -100;
static double g_center_temp = -100;
static int g_max_x = -1, g_max_y = -1;
static int g_probe_x = -1, g_probe_y = -1;
static int g_manual_ffc;
static HWND g_hwnd;
static HFONT g_font;
static int sendcmd(unsigned magic, unsigned param, int len) {
unsigned char cmd[8] = {0};
cmd[0] = (unsigned char)(magic);
cmd[1] = (unsigned char)(magic >> 8);
cmd[2] = (unsigned char)(magic >> 16);
cmd[3] = (unsigned char)(magic >> 24);
if (len >= 8) {
cmd[4] = (unsigned char)(param);
cmd[5] = (unsigned char)(param >> 8);
cmd[6] = (unsigned char)(param >> 16);
cmd[7] = (unsigned char)(param >> 24);
}
int xfer = 0;
if (libusb_bulk_transfer(g_h, 0x03, cmd, len, &xfer, 2000)) return -1;
unsigned char resp[0x1000];
if (libusb_bulk_transfer(g_h, 0x82, resp, sizeof(resp), &xfer, 2000)) return -1;
return 0;
}
/* FFC 命令由 render 库通过回调发出(官方 0x6bb6b672 */
static void ffc_cb(void *user, int param) {
(void)user;
sendcmd(0x6bb6b672, (unsigned)param, 8);
FILE *f = fopen("demo3_ffc.txt", "a");
if (f) {
fprintf(f, "frame=%u FFC(%d)\n", g_global_frames, param);
fclose(f);
}
}
/* ---------------- 附加环节:temporal (FUN_18001e920) ----------------
* 官方 dev+0x41fe0 > 1 时启用(live=1,关闭);period==1: EMA 去噪。 */
static unsigned short g_tmp_hist[NPIX];
static unsigned short *g_tmp_ring = g_tmp_hist;
static int g_tmp_cnt;
static int g_tmp_period = 1;
static int g_tmp_enabled;
static int g_tmp_mode = 1;
static void temporal_apply(unsigned short *buf, int thr) {
if (!g_tmp_enabled) return;
g_tmp_cnt++;
if (g_tmp_period < g_tmp_cnt) {
if (g_tmp_period != 1) return;
for (int i = 0; i < NPIX; ++i) {
unsigned prev = g_tmp_hist[i];
g_tmp_hist[i] = buf[i];
int d = (int)buf[i] - (int)prev;
if (d <= thr && -d <= thr && d != 0)
buf[i] = (unsigned short)(buf[i] - (d >> 1));
}
} else {
memcpy(g_tmp_ring, buf, NPIX * 2);
}
if (g_tmp_cnt % g_tmp_period != 0) {
g_tmp_ring += NPIX;
return;
}
g_tmp_ring = g_tmp_hist;
}
/* ---------------- 附加环节:帧差调整 (FUN_1800175a0) ---------------- */
static void frame_diff_adjust(unsigned short *out) {
int offset;
if (g_dev47950 == 0)
offset = -g_dev47948 >> g_dev4c_shift;
else
offset = 0;
if (offset < 0) offset = 0;
if (!g_buf278) return;
for (int i = 0; i < NPIX; ++i) {
int v = (int)out[i] - (int)g_buf278[i] + (offset & 0xffff);
if (v < 0) v = 0;
if (v > 0xffff) v = 0xffff;
out[i] = (unsigned short)v;
}
}
/* ---------------- 附加环节:隔行平滑 (FUN_1800184f0, 0x41fe8 mode 1/3) ---- */
static void interlace_smooth_2x(unsigned short *out) {
int w = W, h = H;
int sw = (w >> 1) + 2, sh = (h >> 1) + 1;
for (int y = 0; y < sh; ++y) {
int sy = y * 2;
if (sy >= h) sy = h - 1;
const unsigned short *s = out + sy * w;
unsigned short *d = g_sm_buf + y * sw;
for (int x = 0; x < sw; ++x) d[x] = s[x < w ? x : w - 1];
}
unsigned int *p4 = (unsigned int *)out;
for (int yy = 0; yy < (h >> 1); ++yy) {
const unsigned short *t = g_sm_buf + yy * sw;
for (int xx = 0; xx < (w >> 1); ++xx) {
unsigned a = t[xx], b = t[xx + 1];
*p4++ = ((b + a) >> 1) << 16 | a;
}
const unsigned short *cur = g_sm_buf + yy * sw;
const unsigned short *nxt = cur + sw;
unsigned int *p4o = p4;
for (int xx = 0; xx < (w >> 1); ++xx) {
unsigned a = cur[xx], b = cur[xx + 1];
unsigned c = nxt[xx + 2], d = nxt[xx + 3];
*p4o++ = ((b + a + c + d) >> 2) << 16 | ((c + a) >> 1);
}
p4 = p4o;
}
}
/* ---------------- 附加环节:隔行平滑 4x (FUN_180018660, mode 2/3/4) ----- */
static void interlace_smooth_4x(unsigned short *out) {
int w = W, h = H;
int sw = (w >> 2) + 2, sh = (h >> 2) + 1;
for (int y = 0; y < sh; ++y) {
int sy = y * 4;
if (sy >= h) sy = h - 1;
const unsigned short *s = out + sy * w;
unsigned short *d = g_sm4_buf + y * sw;
for (int x = 0; x < sw; ++x) d[x] = s[x < w ? x : w - 1];
}
unsigned int *p4 = (unsigned int *)out;
for (int yy = 0; yy < (h >> 2); ++yy) {
const unsigned short *t = g_sm4_buf + yy * sw;
const unsigned short *n = t + sw;
for (int xx = 0; xx < (w >> 2); ++xx) {
unsigned a = t[xx], b = t[xx + 1];
unsigned c = n[xx + 2], d = n[xx + 3];
*p4++ = ((a + a * 2 + b) >> 2) << 16 | a;
*p4++ = ((a + b * 2 + b) >> 2) << 16 | ((b + a) >> 1);
unsigned s3 = a * 3;
*p4++ = ((((b + c + s3) * 3 + d) >> 4) << 16 | ((c + s3) >> 2));
*p4++ = (((c + (d + b * 2 + b + a) * 3) >> 4) << 16 |
((d + (b + a) * 3 + c) >> 3));
*p4++ = ((((a + c) * 3 + d + b) >> 3) << 16 | ((a + c) >> 1));
*p4++ = (((c + (d + b) * 3 + a) >> 3) << 16 | ((d + b + c + a) >> 2));
unsigned s3b = c * 3;
*p4++ = ((((d + s3b + a) * 3 + b) >> 4) << 16 | ((s3b + a) >> 2));
*p4++ = (((a + (b + d * 2 + d + c) * 3) >> 4) << 16 |
((b + (d + c) * 3 + a) >> 3));
}
}
}
/* ---------------- 附加环节:manual gray 路径 (FUN_180017c40) ----------- */
static int g_enh_acc[NPIX]; /* 官方 dev+0x2a8 */
static void gray_path_manual(const unsigned short *nuc, unsigned char *gray160, int enh) {
if (enh == 0) return;
memset(g_enh_acc, 0, sizeof(g_enh_acc));
unsigned v18 = (unsigned)(g_dev24 * 1000) >> g_dev4c_shift;
if (v18 > 0xffff) v18 = 0xffff;
unsigned v19 = ((unsigned)v18 * (unsigned)enh & 0x7fffffff) >> 7;
for (int y = 0; y < H - 6; ++y) {
for (int x = 0; x < W - 6; ++x) {
const unsigned short *p = nuc + y * W + x;
int *acc = g_enh_acc + y * W + x;
unsigned mn = 0xffff, mx = 0, sum = 0;
for (int dy = 0; dy < 4; ++dy)
for (int dx = 0; dx < 4; ++dx) {
unsigned v = p[dy * W + dx * 2];
sum += v;
if (v < mn) mn = v;
if (v > mx) mx = v;
}
if (v19 <= (mx - mn) * 0x20) {
unsigned mean = sum >> 4;
unsigned r = v19;
if (r < mx - mean) r = mx - mean;
if (r < mean - mn) r = mean - mn;
int k = (int)(0x8000 / (r ? r : 1));
for (int dy = 0; dy < 4; ++dy)
for (int dx = 0; dx < 4; ++dx) {
int i = (dy) * W + dx * 2;
acc[i] += ((int)p[i] - (int)mean) * k;
}
}
}
}
for (int y = 0; y < H; ++y) {
int rn = 4;
if (y < 6) rn = (y >> 1) + 1;
else if (H < y + 7) rn = (H - y + 1) >> 1;
for (int x = 0; x < W; ++x) {
int cn = 4;
if (x < 6) cn = (x >> 1) + 1;
else if (W < x + 7) cn = (W - x + 1) >> 1;
int i = y * W + x;
int v = enh * g_enh_acc[i];
if (cn * rn == 0x10)
v = (int)((v >> 0x1f & 0x7ffff) + v) >> 0x13;
else
v = v / (cn * rn * 0x8000);
int gv = (int)gray160[i] + v;
if (gv < 0) gv = 0;
if (gv > 255) gv = 255;
gray160[i] = (unsigned char)gv;
}
}
}
/* ---------------- 附加环节:auto gray 路径 (FUN_180017770) -----------
* 空间权重表官方被裁剪无法提取,用高斯核近似(σ=2.0)。 */
static float g_gauss7[7];
static float g_radial[24];
static void gray_path_auto_prep(void) {
static int done;
if (done) return;
done = 1;
float s2 = 2.0f * 2.0f * 2.0f;
for (int i = 0; i < 7; ++i) {
int dy = i - 3;
g_gauss7[i] = (float)exp(-(double)(dy * dy) / s2);
}
for (int i = 0; i < 24; ++i) {
float d = (float)i / 23.0f;
g_radial[i] = (float)exp(-(double)(d * d) * 8.0);
}
}
static void gray_path_auto(unsigned short *nuc) {
gray_path_auto_prep();
unsigned short tmp[NPIX];
memcpy(tmp, nuc, sizeof(tmp));
for (int y = 3; y < H - 3; ++y) {
for (int x = 3; x < W - 3; ++x) {
unsigned c = tmp[y * W + x];
float wsum = 0.0f, vsum = 0.0f;
for (int dy = -3; dy <= 3; ++dy) {
for (int dx = -3; dx <= 3; dx += 2) {
unsigned v = tmp[(y + dy) * W + x + dx];
int ad = v > c ? (int)v - c : (int)c - v;
if (ad > 23) ad = 23;
float w = g_gauss7[dy + 3] * g_radial[ad];
wsum += w;
vsum += w * (float)v;
}
}
if (wsum > 0.0f) {
int r = (int)(vsum / wsum) + 0x8000;
if (r < 0) r = 0;
if (r > 0xffff) r = 0xffff;
nuc[y * W + x] = (unsigned short)r;
}
}
}
}
/* ---------------- 附加环节:Nx 升采样 (0x19740 模式推广) ------------- */
static unsigned char g_gray_big[1280 * 960];
static void upscale_nx(const unsigned char *gray160, int n, int *ow, int *oh) {
*ow = W * n;
*oh = H * n;
unsigned char *o = g_gray_big;
int lg = n == 2 ? 1 : (n == 4 ? 2 : 3);
for (int y = 0; y < H - 1; ++y) {
for (int x = 0; x < W - 1; ++x) {
unsigned a = gray160[y * W + x];
unsigned b = gray160[y * W + x + 1];
unsigned c = gray160[(y + 1) * W + x];
unsigned d = gray160[(y + 1) * W + x + 1];
for (int j = 0; j < n; ++j) {
for (int i = 0; i < n; ++i) {
unsigned hv = (a * (n - i) + b * i) >> lg;
unsigned vv = (a * (n - j) + c * j) >> lg;
unsigned dg = (a * (n - i) * (n - j) + b * i * (n - j) +
c * (n - i) * j + d * i * j) >> (lg * 2);
unsigned v;
if (j == 0) v = hv;
else if (i == 0) v = vv;
else v = dg;
o[(y * n + j) * (*ow) + x * n + i] = (unsigned char)v;
}
}
}
}
for (int x = 0; x < W - 1; ++x) {
unsigned a = gray160[(H - 1) * W + x];
unsigned b = gray160[(H - 1) * W + x + 1];
unsigned u = gray160[(H - 2) * W + x];
unsigned u2 = gray160[(H - 2) * W + x + 1];
for (int i = 0; i < n; ++i) {
unsigned hv = (a * (n - i) + b * i) >> lg;
unsigned uh = (u * (n - i) + u2 * i) >> lg;
o[(H * n - 1) * (*ow) + x * n + i] = (unsigned char)((3 * hv >> 2) + (uh >> 2));
}
}
for (int y = 0; y < H - 1; ++y) {
unsigned a = gray160[y * W + W - 1];
unsigned u = gray160[(y + 1) * W + W - 1];
unsigned l = gray160[y * W + W - 2];
unsigned l2 = gray160[(y + 1) * W + W - 2];
for (int j = 0; j < n; ++j) {
unsigned vv = (a * (n - j) + u * j) >> lg;
unsigned lh = (l * (n - j) + l2 * j) >> lg;
o[(y * n + j) * (*ow) + W * n - 1] = (unsigned char)((3 * vv >> 2) + (lh >> 2));
}
}
{
unsigned a = gray160[(H - 1) * W + W - 1];
unsigned l = gray160[(H - 1) * W + W - 2];
unsigned u = gray160[(H - 2) * W + W - 1];
unsigned v1 = (3 * a + l) >> 2;
unsigned v2 = (3 * a + u) >> 2;
o[(*oh - 1) * (*ow) + (*ow) - 1] = (unsigned char)((v1 + v2 + a) / 3);
}
}
/* ---------------- render 钩子 ----------------
* post_nuc: 在 NUC+盲元之后、统计之前(官方 0xca30 路径顺序)。
* post_gray: 在 gray160 生成之后、2x 之前。 */
static unsigned short g_nuc_saved[NPIX]; /* manual gray 路径需要 NUC 域 */
static void post_nuc_cb(void *user, unsigned short *nuc) {
(void)user;
int thr = (g_dev24 * 1000 >> 1) >> g_dev4c_shift; /* 0x15680 static */
if (thr > 0xffff) thr = 0xffff;
if (g_tmp_mode > 1) temporal_apply(nuc, thr);
#if OFF_IMPL_230
if (g_ctrl_230 != 0)
frame_diff_adjust(nuc);
#endif
#if OFF_IMPL_41FE8
if (g_ctrl_41fe8 == 1 || g_ctrl_41fe8 == 3)
interlace_smooth_2x(nuc);
if (g_ctrl_41fe8 == 2 || g_ctrl_41fe8 == 3 || g_ctrl_41fe8 == 4) {
interlace_smooth_4x(nuc);
if (g_ctrl_41fe8 == 4)
interlace_smooth_4x(nuc);
}
#endif
#if OFF_IMPL_30
if (g_ctrl_30 >= 1 && g_ctrl_30 <= 4) {
if (g_ctrl_84 == 0)
gray_path_auto(nuc);
else
memcpy(g_nuc_saved, nuc, sizeof(g_nuc_saved));
}
#endif
}
static void post_gray_cb(void *user, unsigned char *gray160) {
(void)user;
#if OFF_IMPL_30
if (g_ctrl_30 >= 1 && g_ctrl_30 <= 4 && g_ctrl_84 != 0)
gray_path_manual(g_nuc_saved, gray160, g_ctrl_30 << 3);
#else
(void)gray160;
#endif
}
/* ---------------- palette + BMPrender 输出 RGB24 → bottom-up BGR ---- */
static void render_bmp(const unsigned char *rgb) {
unsigned char *px = g_bmp + 54;
for (int y = 0; y < VH; ++y) {
const unsigned char *src = rgb + (size_t)y * VW * 3;
unsigned char *dst = px + (size_t)(VH - 1 - y) * VW * 3;
for (int x = 0; x < VW; ++x) {
dst[x * 3 + 0] = src[x * 3 + 2];
dst[x * 3 + 1] = src[x * 3 + 1];
dst[x * 3 + 2] = src[x * 3 + 0];
}
}
if (g_max_x >= 0) {
int mx2 = g_max_x * 2, my2 = (VH - 1 - g_max_y * 2);
for (int k = -3; k <= 3; ++k) {
if (mx2 + k >= 0 && mx2 + k < VW) {
int d2 = my2 * VW * 3 + (mx2 + k) * 3;
px[d2] = 255; px[d2 + 1] = 255; px[d2 + 2] = 255;
}
if (my2 + k >= 0 && my2 + k < VH) {
int d2 = (my2 + k) * VW * 3 + mx2 * 3;
px[d2] = 255; px[d2 + 1] = 255; px[d2 + 2] = 255;
}
}
}
}
static double counts_to_c(double v) {
int64_t x = (int64_t)v * 3 - 5797;
if (x < 0) x = 0;
int lo = 0, hi = 645;
while (lo < hi) {
int mid = (lo + hi + 1) >> 1;
if (mag160c_official_t2e[mid] <= x) lo = mid;
else hi = mid - 1;
}
int i = lo;
if (i > 644) i = 644;
int64_t diff = x - mag160c_official_t2e[i];
int64_t t2 = mag160c_official_t2e[i + 1] - mag160c_official_t2e[i];
int64_t slope = t2 ? (0x1000000 + t2 / 2) / t2 : 0;
int64_t temp = ((slope * diff) >> 12) + ((int64_t)i << 12) - 0x249f0;
return temp / 1000.0;
}
static LRESULT CALLBACK wndproc(HWND hw, UINT msg, WPARAM wp, LPARAM lp) {
switch (msg) {
case WM_PAINT: {
PAINTSTRUCT ps;
HDC dc = BeginPaint(hw, &ps);
HDC mem = CreateCompatibleDC(dc);
HBITMAP bm = CreateCompatibleBitmap(dc, VW, VH);
HGDIOBJ old = SelectObject(mem, bm);
SetDIBitsToDevice(mem, 0, 0, VW, VH, 0, 0, 0, VH, g_bmp + 54,
(BITMAPINFO *)(g_bmp + 14), DIB_RGB_COLORS);
SetStretchBltMode(dc, HALFTONE);
StretchBlt(dc, 10, 10, 640, 480, mem, 0, 0, VW, VH, SRCCOPY);
SelectObject(mem, old);
DeleteObject(bm);
DeleteDC(mem);
SelectObject(dc, g_font);
SetBkMode(dc, TRANSPARENT);
SetTextColor(dc, RGB(220, 220, 220));
int y = 20;
char line[256];
snprintf(line, sizeof(line), "frame : %u", g_fcount);
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
snprintf(line, sizeof(line), "fps : %.1f", g_fps);
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
if (g_probe_x >= 0) {
int32_t tmc = 0;
mag160c_render_probe_temp(g_render, (uint32_t)g_probe_x, (uint32_t)g_probe_y, &tmc);
snprintf(line, sizeof(line), "probe : (%d,%d) %.2f C", g_probe_x, g_probe_y,
tmc / 1000.0);
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
}
if (g_max_x >= 0) {
snprintf(line, sizeof(line), "max : (%d,%d) %.2f C", g_max_x, g_max_y, g_max_temp);
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
}
snprintf(line, sizeof(line), "center: %.2f C", g_center_temp);
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
snprintf(line, sizeof(line), "pipeline: mag160c_render (official full)");
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
int lo, hi, fmin, fmax, mean, std;
mag160c_render_get_stats(g_render, &lo, &hi, &fmin, &fmax, &mean, &std);
snprintf(line, sizeof(line), "shutter: %d sel: %d win: [%d,%d]",
mag160c_render_get_shutter(g_render),
mag160c_render_get_endpoint(g_render), lo, hi);
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
EndPaint(hw, &ps);
break;
}
case WM_LBUTTONDOWN: {
int x = LOWORD(lp), y = HIWORD(lp);
if (x >= 10 && x < 650 && y >= 10 && y < 490) {
g_probe_x = (x - 10) * W / 640;
g_probe_y = 119 - (y - 10) * H / 480;
InvalidateRect(hw, NULL, TRUE);
}
break;
}
case WM_COMMAND:
switch (LOWORD(wp)) {
case 1001:
g_manual_ffc = 1;
SetWindowTextA(hw, "FFC queued");
break;
case 1002: {
char path[MAX_PATH];
SYSTEMTIME st;
GetLocalTime(&st);
snprintf(path, sizeof(path), "thermal_%04d%02d%02d_%02d%02d%02d.bmp",
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
FILE *f = fopen(path, "wb");
if (f) { fwrite(g_bmp, 1, sizeof(g_bmp), f); fclose(f); }
SetWindowTextA(hw, "saved");
break;
}
case 1004: /* recenter = trigger FFC pair (official ref refresh) */
g_manual_ffc = 1;
SetWindowTextA(hw, "FFC queued");
break;
case 1005:
g_probe_x = -1;
InvalidateRect(hw, NULL, TRUE);
break;
}
break;
case WM_ERASEBKGND:
return 1;
case WM_KEYDOWN:
if (wp == VK_ESCAPE) { DestroyWindow(hw); return 0; }
#if OFF_IMPL_41FE8
if (wp == '0') {
g_ctrl_41fe8 = (g_ctrl_41fe8 + 1) % 5;
SetWindowTextA(hw, g_ctrl_41fe8 ? "41fe8 smoothing ON (mode %d)" : "41fe8 OFF");
}
#endif
#if OFF_IMPL_30
if (wp == '9') {
g_ctrl_30 = (g_ctrl_30 + 1) % 5;
SetWindowTextA(hw, g_ctrl_30 ? "0x30 gray path ON (mode %d)" : "0x30 OFF");
}
#endif
#if OFF_IMPL_230
if (wp == '8') {
g_ctrl_230 = !g_ctrl_230;
SetWindowTextA(hw, g_ctrl_230 ? "0x230 frame-diff ON" : "0x230 OFF");
}
#endif
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hw, msg, wp, lp);
}
int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int show) {
(void)prev; (void)cmd; (void)show;
libusb_init(&g_ctx);
/* render 管线初始化(DDT 由 render 库加载) */
mag160c_render_cfg_t cfg;
memset(&cfg, 0, sizeof(cfg));
cfg.width = W;
cfg.height = H;
cfg.ddt_path = OFF_DDT;
cfg.ffc_period = 1800;
cfg.ffc_drift = 250;
cfg.startup_force_ffc = 1;
cfg.warm_frames = 20;
cfg.cdf_pivot_75 = 1;
cfg.ffc_cb = ffc_cb;
cfg.post_nuc_cb = post_nuc_cb;
cfg.post_gray_cb = post_gray_cb;
mag160c_error_t rerr = mag160c_render_init(&g_render, &cfg);
if (rerr != MAG160C_OK) {
MessageBoxA(NULL, "render init failed (need mag160c_official.ddt)", "MAG160C Demo", MB_ICONERROR);
return 1;
}
int ok = 0;
for (int attempt = 0; attempt < 4 && !ok; ++attempt) {
if (attempt > 0) {
if (g_h) libusb_reset_device(g_h);
if (g_h) { libusb_close(g_h); g_h = NULL; }
Sleep(2500);
}
libusb_device **list = NULL;
ssize_t cnt = libusb_get_device_list(g_ctx, &list);
for (ssize_t i = 0; i < cnt && !g_h; ++i) {
struct libusb_device_descriptor d;
libusb_get_device_descriptor(list[i], &d);
if (d.idVendor == 0x833c) libusb_open(list[i], &g_h);
}
libusb_free_device_list(list, 1);
if (!g_h) continue;
libusb_set_configuration(g_h, 2);
libusb_set_configuration(g_h, 1);
if (libusb_claim_interface(g_h, 0) != 0) { libusb_close(g_h); g_h = NULL; continue; }
if (sendcmd(0x6bb6b66b, 0, 4)) continue;
if (sendcmd(0x6bb6b66c, 0, 4)) continue;
if (sendcmd(0x6bb6b66f, 0, 4)) continue;
if (sendcmd(0x6bb6b672, 0, 8)) continue;
Sleep(100);
if (sendcmd(0x6bb6b672, 0, 8)) continue;
Sleep(300);
if (sendcmd(0x6bb6b673, 0, 4)) continue;
Sleep(700);
ok = 1;
}
if (!ok) {
MessageBoxA(NULL, "camera init failed (retried 4x)", "MAG160C Demo", MB_ICONERROR);
mag160c_render_destroy(g_render);
return 1;
}
WNDCLASSA wc = {0};
wc.lpfnWndProc = wndproc;
wc.hInstance = inst;
wc.lpszClassName = "Mag160cDemoV6";
wc.hCursor = LoadCursor(NULL, IDC_CROSS);
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
RegisterClassA(&wc);
g_hwnd = CreateWindowA("Mag160cDemoV6", "MAG160C Thermal Demo v6 (render lib)",
WS_OVERLAPPEDWINDOW, 60, 40, 1000, 620,
NULL, NULL, inst, NULL);
g_font = CreateFontA(18, 0, 0, 0, FW_NORMAL, 0, 0, 0, ANSI_CHARSET,
0, 0, CLEARTYPE_QUALITY, 0, "Consolas");
CreateWindowA("BUTTON", "FFC", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
670, 160, 120, 32, g_hwnd, (HMENU)1001, inst, NULL);
CreateWindowA("BUTTON", "Save BMP", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
800, 160, 120, 32, g_hwnd, (HMENU)1002, inst, NULL);
CreateWindowA("BUTTON", "FFC", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
670, 200, 120, 32, g_hwnd, (HMENU)1004, inst, NULL);
CreateWindowA("BUTTON", "Clear probe", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
820, 200, 100, 32, g_hwnd, (HMENU)1005, inst, NULL);
unsigned sz = 54 + VW * VH * 3;
g_bmp[0] = 'B'; g_bmp[1] = 'M';
g_bmp[2] = (unsigned char)sz; g_bmp[3] = (unsigned char)(sz >> 8);
g_bmp[4] = (unsigned char)(sz >> 16); g_bmp[5] = (unsigned char)(sz >> 24);
g_bmp[10] = 54; g_bmp[14] = 40;
g_bmp[18] = VW & 0xFF; g_bmp[19] = (VW >> 8) & 0xFF;
g_bmp[20] = (VW >> 16) & 0xFF; g_bmp[21] = (VW >> 24) & 0xFF;
g_bmp[22] = VH & 0xFF; g_bmp[23] = (VH >> 8) & 0xFF;
g_bmp[24] = (VH >> 16) & 0xFF; g_bmp[25] = (VH >> 24) & 0xFF;
g_bmp[26] = 1; g_bmp[28] = 24;
ShowWindow(g_hwnd, SW_SHOW);
SetWindowTextA(g_hwnd, "starting - official pipeline");
static uint8_t rgb[VW * VH * 3];
static uint8_t full[28 + 40000];
unsigned prev2 = 0xffffffff;
DWORD t0 = GetTickCount();
DWORD lfps_t = t0;
unsigned lfps_c = 0;
DWORD last_frame_t = t0;
DWORD last_reset_t = t0;
for (;;) {
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) goto done;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
unsigned char hdr[64];
int xfer = 0;
if (libusb_bulk_transfer(g_h, 0x81, hdr, sizeof(hdr), &xfer, 200) && xfer < 28) {
DWORD now2 = GetTickCount();
if (now2 - last_frame_t > 3000 && now2 - last_reset_t > 5000) {
sendcmd(0x6bb6b672, 1, 8);
last_reset_t = now2;
}
continue;
}
if (xfer < 28) continue;
unsigned m = (unsigned)hdr[0] | ((unsigned)hdr[1] << 8) |
((unsigned)hdr[2] << 16) | ((unsigned)hdr[3] << 24);
if (m != 0x1bb1b11b) continue;
unsigned c = (unsigned)hdr[4] | ((unsigned)hdr[5] << 8) |
((unsigned)hdr[6] << 16) | ((unsigned)hdr[7] << 24);
if (c == prev2) continue;
prev2 = c;
if (libusb_bulk_transfer(g_h, 0x81, g_frame, sizeof(g_frame), &xfer, 200) ||
xfer < 38400)
continue;
last_frame_t = GetTickCount();
g_global_frames++;
/* 组装完整帧(28B 头 + 38400 像素 + 28B 尾,尾部含快门字) */
memcpy(full, hdr, 28);
memcpy(full + 28, g_frame, 38400);
if (xfer > 38400)
memcpy(full + 28 + 38400, g_frame + 38400, (size_t)(xfer - 38400));
if (g_manual_ffc) {
g_manual_ffc = 0;
mag160c_render_request_ffc(g_render);
}
mag160c_error_t rc = mag160c_render_frame(g_render, full, 1, rgb);
if (rc != MAG160C_OK) continue; /* startup/FFC window: frozen */
g_fcount++;
/* hot spot + temps */
unsigned hotv = 0;
int hot = -1;
for (int i = 0; i < NPIX; ++i) {
uint32_t v = mag160c_render_get_nuc(g_render, (uint32_t)i);
if (v > hotv) { hotv = v; hot = i; }
}
if (hot >= 0 && hotv) {
g_max_x = hot % W;
g_max_y = hot / W;
g_max_temp = counts_to_c((double)hotv);
}
int lo, hi, fmin, fmax, mean, std;
mag160c_render_get_stats(g_render, &lo, &hi, &fmin, &fmax, &mean, &std);
g_center_temp = counts_to_c((double)mean);
render_bmp(rgb);
DWORD now = GetTickCount();
if (now - lfps_t >= 1000) {
g_fps = (g_fcount - lfps_c) * 1000.0 / (now - lfps_t);
lfps_t = now;
lfps_c = g_fcount;
}
snprintf(g_title, sizeof(g_title),
"MAG160C v6 render-lib - frame %u shutter %d sel %d win [%d,%d]",
g_global_frames, mag160c_render_get_shutter(g_render),
mag160c_render_get_endpoint(g_render), lo, hi);
SetWindowTextA(g_hwnd, g_title);
if (g_fcount % 30 == 0 && g_fcount / 30 < 40) {
char pth[MAX_PATH];
snprintf(pth, sizeof(pth), "demo3_auto_%03u.bmp", g_fcount / 30);
FILE *f = fopen(pth, "wb");
if (f) { fwrite(g_bmp, 1, sizeof(g_bmp), f); fclose(f); }
}
InvalidateRect(g_hwnd, NULL, FALSE);
UpdateWindow(g_hwnd);
}
done:
Sleep(300);
libusb_clear_halt(g_h, 0x03);
libusb_clear_halt(g_h, 0x82);
sendcmd(0x6bb6b674, 0, 4);
mag160c_render_destroy(g_render);
libusb_close(g_h);
libusb_exit(g_ctx);
return 0;
}