Files
MAG160C/csdk/tools/mag160c_demo3.c
T
ZXCLI df6c6dd444 移植官方附加环节全部路径:帧差/隔行平滑/gray覆盖/Nx升采样(编译开关+按键测试)
- OFF_IMPL_230: 帧差调整 0x175a0 逐行移植(0x278 官方 NULL 时 no-op)
- OFF_IMPL_41FE8: 隔行平滑 0x184f0(2x)/0x18660(4x) 逐行翻译(4相位加权插值)
- OFF_IMPL_30: gray 覆盖路径——0x17c40 manual 逐行翻译(8x8窗口4x4采样
  对比度增强累积→gray);0x17770 auto 双边滤波结构移植(官方空间权重表
  在 .data 被裁剪无法提取,用标准高斯核近似,文档注明)
- OFF_IMPL_UPSCALE: Nx 升采样 4x/8x(2x 已验证模式推广,线性插值+边缘外推)
- 运行时按键切换:0=41fe8 模式、8=0x230、9=0x30 模式
- 验证:默认版无回归;全开版运行正常,41fe8=1 平滑生效(nuc std 250→95)
- 文档:csdk/README 开关说明、csdk/README 验证状态更新
2026-08-14 16:52:52 +08:00

1470 lines
59 KiB
C
Raw 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 v5 - FULL official pipeline replication.
*
* Pixel-verified against CoreSDKLib.dll (Ghidra decompile + live same-frame
* captures, 2026-08-13):
*
* 1. DDT file load (%TEMP%\Core<serial> / mag160c_official.ddt)
* v3 header {magic 0x5aa50003, w, h, epcount, nsegs, 0x47944,
* 0x47948, 0x4155c, 0x41560} + endpoint temps T[epcount] +
* per-endpoint tables {thr (nsegs-1)*npix+0x4155c/2 i16,
* gain/off nsegs*npix*2 u16} + blind records (0x28 bytes each).
* 2. Endpoint select: sel = #T[i] < shutter (shutter = tail word at
* frame offset len+0x24, i.e. g_frame[0x9608]); cap at epcount-2.
* Verified live: shutter 29289 -> sel=2 (T=28495..33637).
* 3. Q12 interpolation: t = ((shutter-T[sel])<<12)/(T[sel+1]-T[sel]),
* clamp [-0x3fff,0x3fff]; work = a + ((b-a)*t>>12).
* Verified 0/38400 thr, 0/115200 gain.
* 4. Ref = 4-frame mean collected on startup/post-FFC frames 6..9
* (reset at frame 6), FFC(1) at frame 9. Frozen between FFCs.
* 5. NUC: d2 = (f20-ref)>>1; seg by pixel thresholds; out = off +
* (gain*d2>>12); clamp. + blind comp (records: avg of N neighbours).
* Verified 0/19200 vs official raw.
* 6. Window: [min(fmin, mean-half), max(fmax, mean+half)],
* half = max(128, 0x24*1000>>0x4794c)/2 (live: 5*1000>>3/2 = 312).
* Stats: min/max/mean/std -> dev+0x42010..24.
* 7. LUT1024 rebuild: 1024-bin hist -> 3-tap smooth -> CDF both
* directions from mean-bin -> contrast curve -> center binary
* search -> LUT fill (+ temporal center smoothing).
* gray = LUT[(nuc-lo)*0xffc00000/(hi-lo)>>22].
* 8. 2x bilinear upscale (exact weights, edge extrapolation).
* Verified 0/76800 vs official 320x240 gray.
* 9. Palette: captured official 256x4 BGR.
* 10. FFC: period 1800 frames or |shutter drift| > 250; after FFC(0)
* the 13-frame startup cycle repeats (rebuild/ref/rebuild).
*
* Ghost behavior is the official one: the ref is frozen between FFCs and
* re-collected (4 frames) after each FFC, so a moving object's old
* position reads as a mild dark residual exactly like the official app.
*/
#define _WIN32_WINNT 0x0601
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <libusb.h>
#include "mag160c/mag160c.h"
#include "mag160c/mag160c_display.h"
#include "mag160c_official_palette256.h"
#include "mag160c_official_t2e.h"
#define MAG_T2E_OFFSET 0x249f0
#define TEMP_C 5797
static int counts_to_temp_mc(int counts) {
int64_t x = (int64_t)counts * 3 - TEMP_C;
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) - MAG_T2E_OFFSET;
return (int)temp;
}
static double counts_to_c(double v) {
return counts_to_temp_mc((int)v) / 1000.0;
}
#define W 160
#define H 120
#define NPIX (W * H)
#define VW 320
#define VH 240
/* official timing constants (live: dev+0x2d78..0x2d94 = 4,5,4,1800,250) */
#define OFF_N0 4 /* ref frames after FFC(1) window start */
#define OFF_N1 5 /* table rebuild frames */
#define OFF_N2 4 /* second table rebuild frames */
#define OFF_FFC_PERIOD 1800
#define OFF_FFC_DRIFT 250
#define OFF_WARM_FRAMES 20 /* mode 1/2: drop first 20 frames */
#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_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 unsigned short g_live[NPIX];
/* ---- official DDT state ---- */
static int g_ep_count;
static int g_nsegs;
static int g_c5c; /* 0x4155c */
static int g_c60; /* 0x41560 */
static int g_dev24 = 5; /* window base */
static int g_dev4c_shift = 3; /* 0x4794c */
static int g_dev48; /* contrast -100..100 */
static int g_dev4c; /* window scale -100..100 */
static int g_dev8c = 1; /* 0x8c: CDF pivot at 75% cumulative (live=1) */
static int g_dev47950 = 0; /* 0x47950: 0 static / 1 time-based */
static int g_dev47948 = -50000; /* 0x47948: comp offset */
static int g_dev47954 = 0; /* 0x47954 */
static int g_shift; /* 0x11c = 12 */
static int g_ep_temps[64]; /* T[] */
static int g_blind_cnt[64];
static short *g_ep_thr[64]; /* per endpoint (nsegs-1)*npix + c5c/2 */
static unsigned short *g_ep_gain[64]; /* per endpoint nsegs*npix*2 */
static unsigned int *g_ep_blind[64]; /* records: target,type,neigh[8] */
static int g_ep_loaded[64];
static int g_sel; /* selected endpoint */
static int g_sel_prev;
static short g_thr_work[NPIX * 2 + 4096]; /* (nsegs-1)*npix + c5c/2 */
static unsigned short g_gain_work[NPIX * 3 * 2]; /* nsegs*npix*2 */
static unsigned int *g_blind_work;
static int g_blind_count;
static int g_tables_ready;
/* ---- official frame state ---- */
static int g_ffc_idx = -1; /* frame index in the FFC cycle (-1 = pre-init) */
static int g_global_frames; /* frames since start */
static int g_shutter; /* current frame shutter (device temp proxy) */
static int g_shutter_at_ffc; /* shutter at last FFC(0) */
static int g_has_ref;
static unsigned short g_ref[NPIX]; /* ref smoother output */
static unsigned int g_ref_acc[NPIX]; /* u32 acc */
static int g_ref_cnt;
static int g_f20[NPIX];
static int g_fcount;
static int g_manual_ffc;
static int g_startup_ffc_done;
static int g_flag4203c; /* official 0x4203c: auto-gray-path marker */
static int g_flag42038; /* official 0x42038: temporal ran this frame */
static int g_flag42004; /* official 0x42004: NUC output valid this frame */
/* ---- render state ---- */
static unsigned short g_nuc[NPIX];
static unsigned int g_hist1024[1024];
static unsigned int g_cdf[1024];
static unsigned char g_lut[1024];
static int g_win_lo, g_win_hi;
static int g_stat_min, g_stat_max, g_stat_mean, g_stat_std;
static unsigned char g_gray160[NPIX];
static unsigned char g_gray320[320 * 240];
static int g_center_prev = 341;
static int g_center_ema = 341;
static int g_mean_prev;
static double g_fps;
static double g_max_temp = -100;
static double g_center_temp = -100;
static int g_probe_x = -1, g_probe_y = -1;
static int g_max_x = -1, g_max_y = -1;
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;
}
/* ---------------- DDT loading (FUN_18000f130 v3 format) ---------------- */
static int load_ddt(const char *path) {
FILE *f = fopen(path, "rb");
if (!f) return 0;
unsigned char hdr[256];
size_t got = fread(hdr, 1, 0x100, f);
if (got < 0x98) { fclose(f); return 0; }
unsigned magic = *(unsigned *)(hdr + 0);
unsigned w = *(unsigned *)(hdr + 4);
unsigned hh = *(unsigned *)(hdr + 8);
if (magic != 0x5aa50003 && magic != 0x5aa50004) { fclose(f); return 0; }
if (w != W || hh != H) { fclose(f); return 0; }
g_ep_count = *(int *)(hdr + 12);
g_nsegs = *(int *)(hdr + 16);
if (g_ep_count < 2 || g_ep_count > 64 || g_nsegs < 1 || g_nsegs > 8) { fclose(f); return 0; }
int v4 = (magic == 0x5aa50004);
g_dev24 = *(int *)(hdr + 20); /* v3: 0x47944 slot (power-of-2) */
g_dev4c_shift = 3;
if (!v4) {
g_c5c = *(int *)(hdr + 28);
g_c60 = *(int *)(hdr + 32);
if (g_c5c > 0x10000) g_c5c = 0;
} else {
g_c5c = *(int *)(hdr + 0x2c + 0x120);
g_c60 = *(int *)(hdr + 0x30 + 0x120);
}
if (g_c5c & 1) g_c5c &= ~1;
/* read arrays */
int arro = v4 ? 0x120 + 36 : 36;
if (fseek(f, arro, SEEK_SET)) { fclose(f); return 0; }
if (fread(g_ep_temps, 4, g_ep_count, f) != (size_t)g_ep_count) { fclose(f); return 0; }
unsigned tmp[64];
if (fread(tmp, 4, g_ep_count, f) != (size_t)g_ep_count) { fclose(f); return 0; }
if (fread(tmp, 4, g_ep_count, f) != (size_t)g_ep_count) { fclose(f); return 0; }
if (fread(g_blind_cnt, 4, g_ep_count - 1, f) != (size_t)(g_ep_count - 1)) { fclose(f); return 0; }
g_blind_cnt[g_ep_count - 1] = 0;
for (int i = 0; i < g_ep_count; ++i)
if (g_blind_cnt[i] > 4096) g_blind_cnt[i] = 4096;
size_t thr_sz = (size_t)(g_nsegs - 1) * NPIX + g_c5c / 2;
size_t gain_sz = (size_t)g_nsegs * NPIX * 2;
for (int e = 0; e < g_ep_count; ++e) {
g_ep_thr[e] = malloc(thr_sz * 2);
g_ep_gain[e] = malloc(gain_sz * 2);
g_ep_blind[e] = malloc((size_t)g_blind_cnt[e] * 40);
if (!g_ep_thr[e] || !g_ep_gain[e] ||
(g_blind_cnt[e] && !g_ep_blind[e])) { fclose(f); return 0; }
if (fread(g_ep_thr[e], 2, thr_sz, f) != thr_sz) { fclose(f); return 0; }
if (fread(g_ep_gain[e], 2, gain_sz, f) != gain_sz) { fclose(f); return 0; }
g_ep_loaded[e] = 1;
}
/* blind records live AFTER all endpoint blocks, one block per endpoint
* (EP0..EPcount-2; EPcount-1 has none). The official file layout puts
* them at the end of the file, NOT inside each endpoint block. */
for (int e = 0; e < g_ep_count - 1; ++e) {
if (g_blind_cnt[e] &&
fread(g_ep_blind[e], 40, g_blind_cnt[e], f) != (size_t)g_blind_cnt[e]) {
fclose(f); return 0;
}
}
fclose(f);
g_shift = 12;
g_tables_ready = 1;
return 1;
}
/* endpoint select (0x180016ae0): sel = #T[i+1] < shutter, capped epcount-2 */
static int select_endpoint(int shutter) {
int sel = 0;
if (g_ep_count != 2) {
while (sel < g_ep_count - 2 && shutter > g_ep_temps[sel + 1]) sel++;
}
return sel;
}
/* Q12 interp (0x180016dd0 / 0x180016f10) */
static void interp_i16(const short *a, const short *b, short *dst, int n, int t) {
for (int i = 0; i < n; ++i) {
dst[i] = (short)((int)a[i] + (((int)b[i] - (int)a[i]) * t >> 12));
}
}
static void interp_u16(const unsigned short *a, const unsigned short *b,
unsigned short *dst, int n, int t) {
for (int i = 0; i < n; ++i) {
dst[i] = (unsigned short)((int)a[i] + (((int)b[i] - (int)a[i]) * t >> 12));
}
}
static void rebuild_tables(int shutter) {
g_sel = select_endpoint(shutter);
int sel = g_sel;
int t = ((shutter - g_ep_temps[sel]) << 12) / (g_ep_temps[sel + 1] - g_ep_temps[sel]);
if (t > 0x3fff) t = 0x3fff;
if (t < -0x3fff) t = -0x3fff;
interp_i16(g_ep_thr[sel], g_ep_thr[sel + 1], g_thr_work,
(g_nsegs - 1) * NPIX + g_c5c / 2, t);
interp_u16(g_ep_gain[sel], g_ep_gain[sel + 1], g_gain_work,
g_nsegs * NPIX * 2, t);
g_blind_work = g_ep_blind[sel];
g_blind_count = g_blind_cnt[sel];
}
/* ---------------- ref smoother (FUN_1800011a0 mode=N) ---------------- */
/* returns 1 when the target count is reached */
static int ref_push(const unsigned short *frame) {
if (g_ref_cnt == 0) {
for (int i = 0; i < NPIX; ++i) g_ref_acc[i] = frame[i];
} else {
for (int i = 0; i < NPIX; ++i) g_ref_acc[i] += frame[i];
}
g_ref_cnt++;
if (g_ref_cnt >= OFF_N0) {
long long accsum = 0;
for (int i = 0; i < NPIX; ++i) accsum += g_ref_acc[i];
long long fsum = 0;
for (int i = 0; i < NPIX; ++i) fsum += frame[i];
FILE *f2 = fopen("demo3_ref.txt", "a");
if (f2) { fprintf(f2, "complete: acc_mean=%.0f last_frame_mean=%.0f cnt=%d acc0=%u f0=%u acc1=%u f1=%u\n",
(double)accsum / NPIX, (double)fsum / NPIX, g_ref_cnt,
g_ref_acc[0], frame[0], g_ref_acc[1], frame[1]); fclose(f2); }
for (int i = 0; i < NPIX; ++i)
g_ref[i] = (unsigned short)(g_ref_acc[i] >> 2);
g_ref_cnt = 0;
g_has_ref = 1;
return 1;
}
return 0;
}
/* ---------------- NUC + blind (0x180017200 + 0x180017330) ---------------- */
static void official_nuc_and_blind(const unsigned short *f20, unsigned short *out) {
const short *thr = g_thr_work;
const unsigned short *gg = g_gain_work;
for (int i = 0; i < NPIX; ++i) {
int d2 = ((int)f20[i] - (int)g_ref[i]) >> 1;
int seg = 0;
if (d2 > thr[i * 2]) {
seg = 1;
if (d2 > thr[i * 2 + 1]) seg = 2;
}
const unsigned short *p = gg + (seg * NPIX + i) * 2;
int v = (int)p[1] + ((int)p[0] * d2 >> 12);
if (v < 0) v = 0;
if (v > 65535) v = 65535;
out[i] = (unsigned short)v;
}
/* blind compensation: fixed records */
if (g_blind_work && g_blind_count > 0) {
for (int r = 0; r < g_blind_count; ++r) {
const unsigned int *rec = g_blind_work + r * 10;
unsigned target = rec[0];
unsigned typ = rec[1];
const unsigned int *ng = rec + 2;
if (target >= NPIX || typ < 3 || typ > 8) continue;
long sum = 0;
for (unsigned j = 0; j < typ; ++j) sum += out[ng[j]];
if (typ == 8) out[target] = (unsigned short)(sum >> 3);
else if (typ == 4) out[target] = (unsigned short)(sum >> 2);
else out[target] = (unsigned short)(sum / typ);
}
}
}
/* ---------------- stats + window (0x180010780 + 0x1800109c0) ---------------- */
static void official_stats_window(const unsigned short *nuc) {
unsigned long long ssum = 0, s2sum = 0;
unsigned mn = 0xffff, mx = 0;
int mi = 0, xi = 0;
for (int i = 0; i < NPIX; ++i) {
unsigned v = nuc[i];
ssum += v;
s2sum += (unsigned long long)v * v;
if (v < mn) { mn = v; mi = i; }
if (v > mx) { mx = v; xi = i; }
}
g_stat_min = (int)mn;
g_stat_max = (int)mx;
g_stat_mean = (int)(ssum / NPIX);
double dmean = (double)ssum / NPIX;
double var = (double)s2sum / NPIX - dmean * dmean;
g_stat_std = (int)sqrt(var > 0 ? var : 0);
(void)mi; (void)xi;
/* official 0x109c0 uniform-frame branch: fmin==fmax -> window pinned,
* LUT/gray reset to mid level, flag 0x4203c set (auto path marker). */
if (mn == mx) {
g_win_lo = g_win_hi = (int)mn;
memset(g_lut, 0x80, sizeof(g_lut));
memset(g_gray160, 0x80, sizeof(g_gray160));
g_flag4203c = 1;
return;
}
g_flag4203c = 0;
/* window half (FUN_1800108a0): base = max(0x80, dev24*1000>>shift) */
int base = (g_dev24 * 1000) >> g_dev4c_shift;
if (base < 0x80) base = 0x80;
/* dev4c scale adjust (magic: half = base + base*dev4c*k) */
if (g_dev4c != 0) {
int c = g_dev4c < 0 ? -0x51eb851f : -0x1b4e81b5;
long long m = (long long)c * (long long)(base * g_dev4c) >> 32;
base += (m >> 4) - (m >> 31);
}
int half = base >> 1;
/* temperature-dependent growth (only 1-2 endpoint DDTs; this unit has 6) */
if (g_tables_ready && g_ep_count <= 2) {
int te = g_ep_temps[g_ep_count - 1] + g_ep_temps[0] / 2;
(void)te;
}
int lo = g_stat_mean - half;
if ((unsigned)lo > (unsigned)g_stat_min) lo = g_stat_min;
if (lo < 0) lo = 0;
int hi = g_stat_mean + half;
if ((unsigned)hi < (unsigned)g_stat_max) hi = g_stat_max;
if (hi > 65535) hi = 65535;
g_win_lo = lo;
g_win_hi = hi;
if (g_win_hi <= g_win_lo) g_win_hi = g_win_lo + 1;
}
/* ---------------- LUT1024 rebuild (FUN_180011950 + FUN_180011ee0) ------- */
static void lut_rebuild(const unsigned short *nuc) {
/* 1. 1024-bin histogram (FUN_180011950 binning, u32 arithmetic) */
int span = g_win_hi - g_win_lo;
unsigned S = 0xFFC00000u / (unsigned)span;
memset(g_hist1024, 0, sizeof(g_hist1024));
for (int i = 0; i < NPIX; ++i) {
unsigned idx = ((unsigned)nuc[i] - (unsigned)g_win_lo) * S >> 0x16;
if (idx < 1024) g_hist1024[idx]++;
}
/* 2. 3-tap in-place smoothing (bins 0..1022) */
for (int i = 0; i < 1023; i += 3) {
g_hist1024[i] = (g_hist1024[i + 1] + g_hist1024[i]) >> 1;
g_hist1024[i + 1] = (g_hist1024[i + 2] + g_hist1024[i + 1]) >> 1;
g_hist1024[i + 2] = (g_hist1024[i + 3] + g_hist1024[i + 2]) >> 1;
}
unsigned total = 0;
for (int i = 0; i < 1024; ++i) total += g_hist1024[i];
unsigned u21 = total >> 12;
if (u21 == 0) u21 = 1;
int lres = ((g_stat_mean - g_win_lo) * 0x3ff) / span;
if (lres < 0) lres = 0;
if (lres > 1023) lres = 1023;
/* official 0x8c=1 (live): CDF center = max(mean-bin, first bin whose
* cumulative count reaches total*3/4). This moves the equalization
* pivot to the 75% accumulation point (FUN_180011ee0). */
if (g_dev8c) {
unsigned acc = 0;
int k = 0;
while (k < 1024) {
acc += g_hist1024[k];
if (acc >= (total * 3 >> 2)) break;
k++;
}
if (k < 1024 && k > lres) lres = k;
}
/* 3. CDF: down from lres, up from lres+1 */
unsigned u11 = 0, u13 = 0;
for (int k = lres; k >= 0; --k) {
u11 += g_hist1024[k];
if (u21 <= u11) { u13 += 0x100; u11 = 0; }
g_cdf[k] = ((0x10000 / u21) * u11 >> 8) + u13;
}
u11 = 0; u13 = 0;
for (int k = lres + 1; k < 1024; ++k) {
u11 += g_hist1024[k];
if (u11 < u21 * 8) {
if (u21 <= u11) { u13 += 0x100; u11 = 0; }
} else {
u13 += 0x200;
u11 = 0;
}
g_cdf[k] = ((0x10000 / u21) * u11 >> 8) + u13;
}
/* 4. contrast curve + center binary search + LUT fill */
static unsigned curve[1024];
for (int k = 0; k < 1024; ++k) {
unsigned v = (0x300000u + (unsigned)k * 0x800) >> 0xd;
curve[k] = v ? v : 1;
}
int iv6 = (g_dev48 * 0x155) / 200;
int iv7 = iv6 + 0x155;
int iv26 = iv7;
if (iv6 + 0x156 > 0x331) iv26 = 0x330;
int iv10 = 0x332;
int b3 = 1;
unsigned iv22 = 0xFFC00000u / (unsigned)span;
while (iv26 + 1 < iv10) {
iv7 = (iv10 + iv26) / 2;
if (b3) { b3 = 0; iv7 = iv26; }
/* fill LUT for center iv7 */
unsigned u5 = g_cdf[0];
if (u5 != 0) {
unsigned u21v = (unsigned)(iv7 * 0x10 + 0x10);
for (int k = lres; k >= 0; --k) {
unsigned u12 = (unsigned)(iv7 * 0x10 + 0x10) -
(g_cdf[k] * ((iv7 * 0x40000u + 0x40000u) / u5) >> 0xe);
if (curve[k] < u21v - u12) u12 = u21v - curve[k];
g_lut[k] = (unsigned char)(u12 >> 6);
u21v = u12;
}
}
u5 = g_cdf[1023];
if (u5 != 0) {
unsigned u21v = (unsigned)(iv7 * 0x10);
for (int k = lres + 1; k < 1024; ++k) {
unsigned u12 = (g_cdf[k] * ((0xffc0000u - iv7 * 0x40000u) / u5) >> 0xe) +
(unsigned)(iv7 * 0x10);
if (curve[k] < u12 - u21v) u12 = curve[k] + u21v;
g_lut[k] = (unsigned char)(u12 >> 6);
u21v = u12;
}
}
int iv4 = iv7;
unsigned bfmin = ((unsigned)g_stat_min - (unsigned)g_win_lo) * iv22 >> 0x16;
unsigned bfmax = ((unsigned)g_stat_max - (unsigned)g_win_lo) * iv22 >> 0x16;
if (bfmin < 1024 && bfmax < 1024 &&
g_lut[bfmin] == 0 && g_lut[bfmax] != 0xff) {
iv4 = iv10;
iv26 = iv7;
}
iv10 = iv4;
}
/* 5. temporal center smoothing + refill */
int dm = g_stat_mean - g_mean_prev;
if (dm < 0) dm = -dm;
g_mean_prev = g_stat_mean;
int iv18 = iv10 + ((dm & 0xffff) << g_dev4c_shift) * 2 + g_stat_mean;
if (iv18 < 0x9c4) {
if (iv18 < 100) iv18 = 100;
iv7 = (iv18 * iv7 + g_center_prev * 500) / (iv18 + 500);
g_center_prev = iv7;
unsigned u5 = g_cdf[0];
if (u5 != 0) {
unsigned u21v = (unsigned)(iv7 * 0x10 + 0x10);
for (int k = lres; k >= 0; --k) {
unsigned u12 = (unsigned)(iv7 * 0x10 + 0x10) -
(g_cdf[k] * ((iv7 * 0x40000u + 0x40000u) / u5) >> 0xe);
if (curve[k] < u21v - u12) u12 = u21v - curve[k];
g_lut[k] = (unsigned char)(u12 >> 6);
u21v = u12;
}
}
u5 = g_cdf[1023];
if (u5 != 0) {
unsigned u21v = (unsigned)(iv7 * 0x10);
for (int k = lres + 1; k < 1024; ++k) {
unsigned u12 = (g_cdf[k] * ((0xffc0000u - iv7 * 0x40000u) / u5) >> 0xe) +
(unsigned)(iv7 * 0x10);
if (curve[k] < u12 - u21v) u12 = curve[k] + u21v;
g_lut[k] = (unsigned char)(u12 >> 6);
u21v = u12;
}
}
}
}
/* ---------------- temporal filter (FUN_18001e920) ---------------- */
/* Official object at dev+0x41f40: +0x10 history (u16), +0x28 ring write
* pointer, +0x30 counter, +0x34 pixel count, +0x38 period(mode), +0x3c
* enabled. Active when dev+0x41fe0 > 1; live session = 1 (off).
* period==1: out[i] = in[i] - ((in[i]-prev[i])>>1) when |d|<=thr (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; /* dev+0x41f40+0x38 */
static int g_tmp_enabled; /* dev+0x41f40+0x3c */
static int g_tmp_mode = 1; /* dev+0x41fe0 */
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) ---------------- */
/* out[i] = clamp(out[i] - buf278[i] + offset); offset = (-0x47948)>>0x4794c
* (static mode; live: -(-50000)>>3 = 6250). 0x278 buffer is NULL in the
* official session (0x230=0), so this is a no-op unless OFF_IMPL_230 and
* g_buf278 are provided. */
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) ----
* 偶数行每行取前 w/2+2 个样本 → 水平 2-tap 插值写回偶数行,
* 与下一行样本做 4-tap 对角平均写回奇数行(半分辨率降噪插值)。 */
static void interlace_smooth_2x(unsigned short *out) {
int w = W, h = H;
int sw = (w >> 1) + 2, sh = (h >> 1) + 1;
/* subsample: even rows, first sw columns (clamped) */
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];
}
/* horizontal interpolation into even rows */
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;
}
/* vertical 4-tap into odd row */
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) -----
* 逐行翻译:每 4 行取 1 行、每行前 w/4+2 样本 → 4 相位输出,
* 每相位 2 个 u32(4 个 u16)水平/垂直加权插值。 */
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];
/* phase 0: 水平 4x 插值 */
*p4++ = ((a + a * 2 + b) >> 2) << 16 | a;
*p4++ = ((a + b * 2 + b) >> 2) << 16 | ((b + a) >> 1);
/* phase 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));
/* phase 2 */
*p4++ = ((((a + c) * 3 + d + b) >> 3) << 16 | ((a + c) >> 1));
*p4++ = (((c + (d + b) * 3 + a) >> 3) << 16 | ((d + b + c + a) >> 2));
/* phase 3 */
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) -----------
* 0x30>=1 且 0x84!=0 时:8x8 窗口 4x4 采样,高对比度区域做对比度
* 增强累积(0x2a8 int 缓冲),最后累加到 gray0x2a0)。
* param_2 = 增强等级(官方传 0x30 模式 << 3 = 8/16/24/32)。逐行翻译。 */
static int g_enh_acc[NPIX]; /* 官方 dev+0x2a8 */
static void gray_path_manual(const unsigned short *nuc, 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;
}
}
}
}
/* gray accumulate pass (0x2a0) */
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)g_gray160[i] + v;
if (gv < 0) gv = 0;
if (gv > 255) gv = 255;
g_gray160[i] = (unsigned char)gv;
}
}
}
/* ---------------- 附加环节:auto gray 路径 (FUN_180017770) -----------
* 0x30>=1 且 0x84==0 时:0x290 行缓冲边界扩展 + 4行x7列加权双边
* 滤波(空间权重表 0x9a454/460/474 + 值域核 0x9a450/4a0)就地平滑
* 0x270,然后直方图均衡更新 0x2a0。
* 注意:官方 .data 节在二进制中被裁剪,空间权重表静态值无法提取,
* 此处用标准高斯核近似(σ=2.0),值域核按官方生成式(sqrt 表)。
* 官方会话 0x30=0 不执行;启用 OFF_IMPL_30 且 g_ctrl_30>=1 时生效。 */
static float g_gauss7[7]; /* 空间权重(近似,官方表不可提取) */
static float g_radial[24]; /* 值域核 |d|/span 查表(近似 0x9a450 */
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 模式推广) -----------
* 2x 已逐像素验证(0/76800);4x/8x 用同一线性插值模式推广
* (隔 n 采样 + (n-i)/n 权重,垂直 2-tap + 对角双线性,边缘外推)。
* 当前设备固定 2x,仅供 OFF_IMPL_UPSCALE 测试。 */
static unsigned char g_gray_big[1280 * 960];
static void upscale_nx(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); /* log2(n) */
for (int y = 0; y < H - 1; ++y) {
for (int x = 0; x < W - 1; ++x) {
unsigned a = g_gray160[y * W + x];
unsigned b = g_gray160[y * W + x + 1];
unsigned c = g_gray160[(y + 1) * W + x];
unsigned d = g_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;
}
}
}
}
/* bottom row extrapolation */
for (int x = 0; x < W - 1; ++x) {
unsigned a = g_gray160[(H - 1) * W + x];
unsigned b = g_gray160[(H - 1) * W + x + 1];
unsigned u = g_gray160[(H - 2) * W + x];
unsigned u2 = g_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));
}
}
/* right column extrapolation */
for (int y = 0; y < H - 1; ++y) {
unsigned a = g_gray160[y * W + W - 1];
unsigned u = g_gray160[(y + 1) * W + W - 1];
unsigned l = g_gray160[y * W + W - 2];
unsigned l2 = g_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));
}
}
/* corner */
{
unsigned a = g_gray160[(H - 1) * W + W - 1];
unsigned l = g_gray160[(H - 1) * W + W - 2];
unsigned u = g_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);
}
}
static void gray_map(const unsigned short *nuc) {
unsigned S = 0xFFC00000u / (unsigned)(g_win_hi - g_win_lo);
for (int i = 0; i < NPIX; ++i) {
unsigned v = nuc[i];
unsigned idx;
if (v <= (unsigned)g_win_lo) idx = 0;
else if (v >= (unsigned)g_win_hi) idx = 1023;
else idx = (v - (unsigned)g_win_lo) * S >> 0x16;
if (idx > 1023) idx = 1023;
g_gray160[i] = g_lut[idx];
}
}
static void upscale2x(void) {
const unsigned char *s = g_gray160;
unsigned char *o = g_gray320;
int W2 = W * 2;
for (int y = 0; y < H - 1; ++y) {
const unsigned char *r0 = s + y * W;
const unsigned char *r1 = r0 + W;
unsigned char *o0 = o + y * W2 * 2;
unsigned char *o1 = o0 + W2;
int x;
for (x = 0; x < W - 2; x += 2) {
unsigned a0 = r0[x], a1 = r0[x + 1], a2 = r0[x + 2];
unsigned b0 = r1[x], b1 = r1[x + 1], b2 = r1[x + 2];
int c = x * 2;
o0[c] = (unsigned char)a0;
o0[c + 1] = (unsigned char)((a0 + a1) >> 1);
o0[c + 2] = (unsigned char)a1;
o0[c + 3] = (unsigned char)((a2 + a1) >> 1);
o1[c] = (unsigned char)((b0 + a0) >> 1);
o1[c + 1] = (unsigned char)((b0 + b1 + a0 + a1) >> 2);
o1[c + 2] = (unsigned char)((b1 + a1) >> 1);
o1[c + 3] = (unsigned char)((b2 + b1 + a2 + a1) >> 2);
}
/* edge pair x = W-2 */
unsigned a0 = r0[W - 2], a1 = r0[W - 1];
unsigned b0 = r1[W - 2], b1 = r1[W - 1];
int c = (W - 2) * 2;
o0[c] = (unsigned char)a0;
o0[c + 1] = (unsigned char)((a1 + a0) >> 1);
o1[c] = (unsigned char)((b0 + a0) >> 1);
o1[c + 1] = (unsigned char)((a1 + b1 + b0 + a0) >> 2);
/* last column */
c = (W - 1) * 2;
o0[c] = (unsigned char)a1;
o0[c + 1] = (unsigned char)((3 * a1 >> 2) + (a0 >> 2));
o1[c] = (unsigned char)((b1 + a1) >> 1);
o1[c + 1] = (unsigned char)((3 * (b1 + a1) + b0 + a0) >> 3);
}
/* bottom row */
{
int y = H - 1;
const unsigned char *r0 = s + y * W;
const unsigned char *r1 = r0 - W;
unsigned char *o0 = o + y * W2 * 2;
unsigned char *o1 = o0 + W2;
int x;
for (x = 0; x < W - 2; x += 2) {
unsigned a0 = r0[x], a1 = r0[x + 1], a2 = r0[x + 2];
unsigned u0 = r1[x], u1 = r1[x + 1], u2 = r1[x + 2];
int c = x * 2;
o0[c] = (unsigned char)a0;
o0[c + 1] = (unsigned char)((a0 + a1) >> 1);
o0[c + 2] = (unsigned char)a1;
o0[c + 3] = (unsigned char)((a2 + a1) >> 1);
o1[c] = (unsigned char)((3 * a0 >> 2) + (u0 >> 2));
o1[c + 1] = (unsigned char)((3 * (a0 + a1) + u0 + u1) >> 3);
o1[c + 2] = (unsigned char)((3 * a1 >> 2) + (u1 >> 2));
o1[c + 3] = (unsigned char)((3 * (a2 + a1) + u2 + u1) >> 3);
}
unsigned a0 = r0[W - 2], a1 = r0[W - 1];
unsigned u0 = r1[W - 2], u1 = r1[W - 1];
int c = (W - 2) * 2;
o0[c] = (unsigned char)a0;
o0[c + 1] = (unsigned char)((a1 + a0) >> 1);
o1[c] = (unsigned char)((3 * a0 >> 2) + (u0 >> 2));
o1[c + 1] = (unsigned char)((3 * (a0 + a1) + u0 + u1) >> 3);
c = (W - 1) * 2;
o0[c] = (unsigned char)a1;
o0[c + 1] = (unsigned char)((3 * a1 + a0) >> 2);
o1[c] = (unsigned char)((3 * a1 + u1) >> 2);
o1[c + 1] = (unsigned char)((o0[c + 1] + o1[c] + a1) / 3);
}
}
/* ---------------- palette + BMP ---------------- */
static void render_bmp(void) {
unsigned char *px = g_bmp + 54;
/* 官方输出是 320x240:用 2x 升采样后的灰度上色(2-tap 平滑本身
* 就抑制了颗粒感;官方 app 显示的也是这个 2x 缓冲)。 */
for (int y = 0; y < VH; ++y) {
for (int x = 0; x < VW; ++x) {
int i = y * VW + x;
unsigned char gv = g_gray320[i];
unsigned char r = mag160c_official_palette256[gv][2];
unsigned char g = mag160c_official_palette256[gv][1];
unsigned char b = mag160c_official_palette256[gv][0];
int dst = (VH - 1 - y) * VW * 3 + x * 3;
px[dst + 0] = b; px[dst + 1] = g; px[dst + 2] = r;
}
}
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 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) {
double t = counts_to_c((double)g_live[g_probe_y * W + g_probe_x]);
snprintf(line, sizeof(line), "probe : (%d,%d) %.2f C", g_probe_x, g_probe_y, t);
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: official full (DDT+NUC+blind+LUT+2x)");
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
snprintf(line, sizeof(line), "shutter: %d sel: %d win: [%d,%d]", g_shutter, g_sel,
g_win_lo, g_win_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; }
/* runtime toggles for the official extra paths (compile-gated) */
#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);
if (!load_ddt(OFF_DDT)) {
char alt[MAX_PATH];
snprintf(alt, sizeof(alt), "%s\\Core160043865", getenv("TEMP") ? getenv("TEMP") : ".");
if (!load_ddt(alt)) {
MessageBoxA(NULL, "DDT calibration file not found", "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);
return 1;
}
WNDCLASSA wc = {0};
wc.lpfnWndProc = wndproc;
wc.hInstance = inst;
wc.lpszClassName = "Mag160cDemoV5";
wc.hCursor = LoadCursor(NULL, IDC_CROSS);
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
RegisterClassA(&wc);
g_hwnd = CreateWindowA("Mag160cDemoV5", "MAG160C Thermal Demo v5 (official pipeline)",
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");
/* initialize the DDT-based tables with the current shutter (unknown
* until the first frame arrives: use a mid endpoint default) */
g_ffc_idx = -1;
FILE *dbg = fopen("demo3_diag.txt", "w");
FILE *ffcdbg = fopen("demo3_ffc.txt", "a");
unsigned prev2 = 0xffffffff;
DWORD t0 = GetTickCount();
DWORD lfps_t = t0;
unsigned lfps_c = 0;
DWORD last_frame_t = t0;
DWORD last_reset_t = t0;
int ref_reset = 0;
int skipped = 0;
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++;
if (g_global_frames < OFF_WARM_FRAMES) continue;
/* shutter = tail word at frame offset len+0x24 (pixels start at
* g_frame[0]; tail = g_frame + 0x9600 + 0x24 - 0x1c) */
unsigned len = (unsigned)hdr[8] | ((unsigned)hdr[9] << 8) |
((unsigned)hdr[10] << 16) | ((unsigned)hdr[11] << 24);
int shutter = 0;
if (len == 0x9600 && xfer >= 0x9608 + 4)
shutter = (int)((unsigned)g_frame[0x9608] | ((unsigned)g_frame[0x9609] << 8) |
((unsigned)g_frame[0x960a] << 16) | ((unsigned)g_frame[0x960b] << 24));
g_shutter = shutter;
if (g_manual_ffc) {
g_manual_ffc = 0;
sendcmd(0x6bb6b672, 0, 8);
g_ffc_idx = 0;
g_shutter_at_ffc = shutter;
if (ffcdbg) { fprintf(ffcdbg, "frame=%u MANUAL FFC(0) shutter=%d\n", g_global_frames, shutter); fflush(ffcdbg); }
}
/* official FFC state machine (FUN_180009ee0) */
if (g_ffc_idx < 0) {
/* pre-init: send FFC(0), start the cycle */
sendcmd(0x6bb6b672, 0, 8);
g_ffc_idx = 0;
g_shutter_at_ffc = shutter;
if (ffcdbg) { fprintf(ffcdbg, "frame=%u INIT FFC(0) shutter=%d\n", g_global_frames, shutter); fflush(ffcdbg); }
}
g_ffc_idx++;
int idx = g_ffc_idx;
{
unsigned ftype = (unsigned)hdr[12] | ((unsigned)hdr[13] << 8) |
((unsigned)hdr[14] << 16) | ((unsigned)hdr[15] << 24);
long long fsum = 0;
for (int i = 0; i < NPIX; i += 16)
fsum += (unsigned short)(g_frame[i * 2] | ((unsigned)g_frame[i * 2 + 1] << 8));
if (dbg) { fprintf(dbg, "STAGE idx=%d type=%u fmean=%.0f shutter=%d\n", idx, ftype,
(double)fsum / (NPIX / 16), shutter); fflush(dbg); }
if (ftype != 0 && idx == 6) {
static int saved;
if (!saved) {
saved = 1;
FILE *ft = fopen("type1_frame.bin", "wb");
if (ft) { fwrite(g_frame, 1, 38400, ft); fclose(ft); }
}
}
}
if (idx <= OFF_N1) {
if (idx == 1) {
rebuild_tables(g_shutter_at_ffc);
if (dbg) { fprintf(dbg, "rebuild sel=%d T=[%d,%d] shutter=%d\n", g_sel,
g_ep_temps[g_sel], g_ep_temps[g_sel + 1], g_shutter_at_ffc); fflush(dbg); }
}
continue;
}
if (idx <= OFF_N0 + OFF_N1) {
if (idx == OFF_N0 + OFF_N1) {
sendcmd(0x6bb6b672, 1, 8);
if (ffcdbg) { fprintf(ffcdbg, "frame=%u FFC(1)\n", g_global_frames); fflush(ffcdbg); }
}
if (idx == OFF_N1 + 1) {
g_ref_cnt = 0;
g_has_ref = 0;
ref_reset = 1;
}
/* collect ref frame */
for (int i = 0; i < NPIX; ++i)
g_live[i] = (unsigned short)(g_frame[i * 2] | ((unsigned)g_frame[i * 2 + 1] << 8));
if (ref_reset) { ref_reset = 0; g_ref_cnt = 0; }
{
long long lsum = 0;
for (int i = 0; i < NPIX; ++i) lsum += g_live[i];
FILE *f3 = fopen("demo3_ref.txt", "a");
if (f3) { fprintf(f3, " push idx=%d live_mean=%.0f cnt=%d\n", idx,
(double)lsum / NPIX, g_ref_cnt); fclose(f3); }
}
ref_push(g_live);
continue;
}
if (idx <= OFF_N0 + OFF_N2 + OFF_N1) {
continue;
}
/* normal frame: FFC condition (FUN_180009ee0) */
{
int drift = g_shutter - g_shutter_at_ffc;
if (drift < 0) drift = -drift;
int cool = OFF_N0 + OFF_N2 + OFF_N1;
int do_ffc = 0;
/* startup forced FFCs (official): mode 0/3/5/6 forces FFC(0)
* at frame 75 once; mode 1/2/4 forces at frame 117 up to 5
* times. We do not know the live 0x2d47 mode on Windows, so
* follow the mode-0 rule (matches the observed trace cadence:
* an extra FFC shortly after startup). */
if (!g_startup_ffc_done && idx == 75) {
g_startup_ffc_done = 1;
do_ffc = 1;
}
if (idx >= OFF_N0 + OFF_FFC_PERIOD)
do_ffc = 1;
else if (idx >= cool + 30 && drift > OFF_FFC_DRIFT)
do_ffc = 1;
if (do_ffc) {
sendcmd(0x6bb6b672, 0, 8);
g_ffc_idx = 0;
g_shutter_at_ffc = shutter;
if (ffcdbg) { fprintf(ffcdbg, "frame=%u AUTO FFC(0) idx=%d drift=%d%s\n", g_global_frames, idx, drift,
g_startup_ffc_done && idx == 75 ? " (forced75)" : ""); fflush(ffcdbg); }
continue;
}
}
/* decode + render */
for (int i = 0; i < NPIX; ++i)
g_live[i] = (unsigned short)(g_frame[i * 2] | ((unsigned)g_frame[i * 2 + 1] << 8));
g_fcount++;
if (dbg) { fprintf(dbg, "RENDER f=%u has_ref=%d tables=%d\n", g_fcount, g_has_ref, g_tables_ready); fflush(dbg); }
/* official 0xca30: per-frame flag reset (0x4203c/0x42040/0x42044/0x42004) */
g_flag4203c = 0;
g_flag42038 = 0;
g_flag42004 = 0;
if (g_has_ref && g_tables_ready) {
official_nuc_and_blind(g_live, g_nuc);
/* official 0xca30: temporal filter when 0x41fe0 > 1 (live=1, off) */
g_flag42038 = 0;
if (g_tmp_mode > 1) {
int thr = (g_dev24 * 1000 >> 1) >> g_dev4c_shift; /* 0x15680 static */
if (thr > 0xffff) thr = 0xffff;
temporal_apply(g_nuc, thr);
g_flag42038 = 1;
}
#if OFF_IMPL_230
if (g_ctrl_230 != 0)
frame_diff_adjust(g_nuc);
#endif
#if OFF_IMPL_41FE8
if (g_ctrl_41fe8 == 1 || g_ctrl_41fe8 == 3)
interlace_smooth_2x(g_nuc);
if (g_ctrl_41fe8 == 2 || g_ctrl_41fe8 == 3 || g_ctrl_41fe8 == 4) {
interlace_smooth_4x(g_nuc);
if (g_ctrl_41fe8 == 4)
interlace_smooth_4x(g_nuc);
}
#endif
official_stats_window(g_nuc);
lut_rebuild(g_nuc);
gray_map(g_nuc);
#if OFF_IMPL_30
if (g_ctrl_30 >= 1 && g_ctrl_30 <= 4) {
if (g_ctrl_84 == 0)
gray_path_auto(g_nuc);
else
gray_path_manual(g_nuc, g_ctrl_30 << 3);
}
#endif
upscale2x();
g_flag42004 = 1;
} else {
memset(g_nuc, 0, sizeof(g_nuc));
memset(g_gray160, 0, sizeof(g_gray160));
}
/* hot spot + temps */
unsigned hotv = 0;
int hot = 0;
for (int i = 0; i < NPIX; ++i) {
if (g_nuc[i] > hotv && g_nuc[i] != 0) { hotv = g_nuc[i]; hot = i; }
}
if (hot >= 0 && hotv) {
g_max_x = hot % W;
g_max_y = hot / W;
g_max_temp = counts_to_c((double)hotv);
}
g_center_temp = counts_to_c((double)g_stat_mean);
render_bmp();
if (dbg) {
long long rsum = 0, lsum = 0, dsum = 0;
long long dmin = 1 << 30, dmax = -(1 << 30);
for (int i = 0; i < NPIX; ++i) {
rsum += g_ref[i]; lsum += g_live[i];
long long d = (long long)g_live[i] - g_ref[i];
dsum += d;
if (d < dmin) dmin = d;
if (d > dmax) dmax = d;
}
fprintf(dbg, "DIAG ref_mean=%.0f live_mean=%.0f d=(%lld..%lld m%.0f)\n",
(double)rsum / NPIX, (double)lsum / NPIX, dmin, dmax, (double)dsum / NPIX);
fflush(dbg);
}
if (dbg) {
fprintf(dbg, "f=%u shutter=%d sel=%d/%d win=[%d,%d] stats=(%d..%d m%d s%d) ref=%d lut[0]=%d lut[100]=%d lut[1023]=%d\n",
g_fcount, g_shutter, g_sel, g_ep_count, g_win_lo, g_win_hi,
g_stat_min, g_stat_max, g_stat_mean, g_stat_std,
g_has_ref, g_lut[0], g_lut[100], g_lut[1023]);
fflush(dbg);
}
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 v5 official - frame %u idx %d shutter %d sel %d win [%d,%d] skipped %d",
g_global_frames, g_ffc_idx, g_shutter, g_sel, g_win_lo, g_win_hi, skipped);
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); }
}
if (g_fcount == 300 || g_fcount == 600 || g_fcount == 900) {
FILE *fc = fopen("demo3_frame_capture.bin", "wb");
if (fc) {
fwrite(g_live, 2, NPIX, fc);
fwrite(g_ref, 2, NPIX, fc);
fwrite(g_nuc, 2, NPIX, fc);
fwrite(g_gray160, 1, NPIX, fc);
fwrite(g_lut, 1, 1024, fc);
fwrite(g_thr_work, 2, (g_nsegs - 1) * NPIX + g_c5c / 2, fc);
fwrite(g_gain_work, 2, g_nsegs * NPIX * 2, fc);
int meta[8] = {g_win_lo, g_win_hi, g_stat_min, g_stat_max,
g_stat_mean, g_stat_std, g_shutter, g_sel};
fwrite(meta, 4, 8, fc);
fclose(fc);
if (dbg) { fprintf(dbg, "CAPTURED frame data\n"); fflush(dbg); }
}
}
InvalidateRect(g_hwnd, NULL, FALSE);
UpdateWindow(g_hwnd);
}
done:
if (dbg) fclose(dbg);
if (ffcdbg) fclose(ffcdbg);
Sleep(300);
libusb_clear_halt(g_h, 0x03);
libusb_clear_halt(g_h, 0x82);
sendcmd(0x6bb6b674, 0, 4);
libusb_close(g_h);
libusb_exit(g_ctx);
return 0;
}