/* 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 / 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 #include #include #include #include #include #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 缓冲),最后累加到 gray(0x2a0)。 * 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; }