- 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 正常
791 lines
30 KiB
C
791 lines
30 KiB
C
/* 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 + 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;
|
||
}
|