/* 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 #include #include #include #include #include #include #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 + BMP(render 输出 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; }