diff --git a/build-artifacts/mag160c_demo3.exe b/build-artifacts/mag160c_demo3.exe index e65867b..cd79829 100644 --- a/build-artifacts/mag160c_demo3.exe +++ b/build-artifacts/mag160c_demo3.exe @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a85b8a2164ee379b6eda7237976f694d584004d848dfc0000a5159f8b32899c2 -size 139578 +oid sha256:0a85090dc4c671f8686097fca408b7df98f5105aca6c399a21c91b947cf61699 +size 140785 diff --git a/csdk/include/mag160c/mag160c_render.h b/csdk/include/mag160c/mag160c_render.h index 583ecfe..69e6102 100644 --- a/csdk/include/mag160c/mag160c_render.h +++ b/csdk/include/mag160c/mag160c_render.h @@ -29,6 +29,16 @@ extern "C" { /* FFC command callback: param 0 = FFC(0), 1 = FFC(1). */ typedef void (*mag160c_render_ffc_cb_t)(void *user, int param); +/* Optional post-NUC hook (called after NUC+blind, before stats/window). + * Lets the app run the official extra paths (frame-diff 0x230, interlace + * smoothing 0x41fe8) on the NUC buffer. */ +typedef void (*mag160c_render_post_nuc_cb_t)(void *user, unsigned short *nuc); + +/* Optional post-gray hook (called after the 160x120 gray is mapped, + * before the 2x upscale). Lets the app run the official 0x30 gray + * overlay paths (0x17770 auto / 0x17c40 manual) on the gray buffer. */ +typedef void (*mag160c_render_post_gray_cb_t)(void *user, unsigned char *gray160); + typedef struct mag160c_render_t mag160c_render_t; typedef struct mag160c_render_cfg_t { @@ -42,6 +52,10 @@ typedef struct mag160c_render_cfg_t { int cdf_pivot_75; /* 0x8c: CDF pivot at 75% cumulative, 1 = on */ mag160c_render_ffc_cb_t ffc_cb; void *ffc_user; + mag160c_render_post_nuc_cb_t post_nuc_cb; + void *post_nuc_user; + mag160c_render_post_gray_cb_t post_gray_cb; + void *post_gray_user; } mag160c_render_cfg_t; MAG160C_API mag160c_error_t @@ -74,6 +88,14 @@ MAG160C_API int mag160c_render_get_endpoint(mag160c_render_t *r); MAG160C_API int mag160c_render_get_frame_index(mag160c_render_t *r); MAG160C_API int mag160c_render_has_reference(mag160c_render_t *r); +/* Manual FFC trigger (official FFC button / recenter): forces FFC(0) at + * the next rendered frame. */ +MAG160C_API void mag160c_render_request_ffc(mag160c_render_t *r); + +/* NUC-domain pixel read (valid when mag160c_render_has_reference()). + * Returns 0 when no reference yet. Used for hot-spot scan / overlay. */ +MAG160C_API uint32_t mag160c_render_get_nuc(mag160c_render_t *r, uint32_t idx); + /* Temperature probe on the current NUC output (millidegrees C). */ MAG160C_API mag160c_error_t mag160c_render_probe_temp(mag160c_render_t *r, uint32_t x, uint32_t y, diff --git a/csdk/src/mag160c_render.c b/csdk/src/mag160c_render.c index 58235b9..9ba9e89 100644 --- a/csdk/src/mag160c_render.c +++ b/csdk/src/mag160c_render.c @@ -36,7 +36,7 @@ struct mag160c_render_t { int blind_count; /* frame/FFC state */ - int ffc_idx, global_frames, shutter, shutter_at_ffc; + int ffc_idx, global_frames, shutter, shutter_at_ffc, manual_ffc; int has_ref, ref_cnt, startup_ffc_done, ref_reset; unsigned short *ref; unsigned int *ref_acc; @@ -54,6 +54,10 @@ struct mag160c_render_t { /* config */ mag160c_render_ffc_cb_t ffc_cb; void *ffc_user; + mag160c_render_post_nuc_cb_t post_nuc_cb; + void *post_nuc_user; + mag160c_render_post_gray_cb_t post_gray_cb; + void *post_gray_user; int ffc_period, ffc_drift, warm_frames, cdf_pivot_75, force75; }; @@ -503,6 +507,10 @@ static int ffc_step(mag160c_render_t *r, int shutter) { if (drift < 0) drift = -drift; int cool = OFF_N0 + OFF_N2 + OFF_N1; int do_ffc = 0; + if (r->manual_ffc) { + r->manual_ffc = 0; + do_ffc = 1; + } if (r->force75 && !r->startup_ffc_done && idx == 75) { r->startup_ffc_done = 1; do_ffc = 1; @@ -533,6 +541,10 @@ mag160c_error_t mag160c_render_init(mag160c_render_t **out, r->npix = r->w * r->h; r->ffc_cb = cfg->ffc_cb; r->ffc_user = cfg->ffc_user; + r->post_nuc_cb = cfg->post_nuc_cb; + r->post_nuc_user = cfg->post_nuc_user; + r->post_gray_cb = cfg->post_gray_cb; + r->post_gray_user = cfg->post_gray_user; r->ffc_period = cfg->ffc_period ? cfg->ffc_period : 1800; r->ffc_drift = cfg->ffc_drift ? cfg->ffc_drift : 250; r->warm_frames = cfg->warm_frames ? cfg->warm_frames : 20; @@ -606,9 +618,13 @@ mag160c_error_t mag160c_render_frame(mag160c_render_t *r, const uint8_t *frame, if (!r->has_ref) return MAG160C_ERR_NOT_READY; nuc_and_blind(r, live, r->nuc); + if (r->post_nuc_cb) + r->post_nuc_cb(r->post_nuc_user, r->nuc); stats_window(r, r->nuc); lut_rebuild(r, r->nuc); gray_map(r, r->nuc); + if (r->post_gray_cb) + r->post_gray_cb(r->post_gray_user, r->gray160); upscale2x(r); /* palette colorize 320x240 */ for (int i = 0; i < r->npix * 4; ++i) { @@ -663,3 +679,12 @@ void mag160c_render_destroy(mag160c_render_t *r) { free(r->gray320); free(r); } + +void mag160c_render_request_ffc(mag160c_render_t *r) { + if (r) r->manual_ffc = 1; +} + +uint32_t mag160c_render_get_nuc(mag160c_render_t *r, uint32_t idx) { + if (!r || !r->has_ref || idx >= (uint32_t)r->npix) return 0; + return r->nuc[idx]; +} diff --git a/csdk/tools/mag160c_demo3.c b/csdk/tools/mag160c_demo3.c index 198c20a..b4c5577 100644 --- a/csdk/tools/mag160c_demo3.c +++ b/csdk/tools/mag160c_demo3.c @@ -1,92 +1,38 @@ -/* MAG160C Windows Demo v5 - FULL official pipeline replication. +/* MAG160C Windows Demo v6 - official pipeline via mag160c_render API. * - * Pixel-verified against CoreSDKLib.dll (Ghidra decompile + live same-frame - * captures, 2026-08-13): + * The whole official frame pipeline now lives in the platform-independent + * module csdk/src/mag160c_render.c (shutter extract -> FFC state machine -> + * endpoint select + Q12 interp -> ref 4x mean -> NUC -> blind -> stats/ + * window -> LUT1024 -> gray -> 2x -> palette). This app only does: + * USB capture (libusb), the display window (GDI), diagnostics and the + * official "extra paths" (0x230 / 0x41fe8 / 0x30) through the render + * post-processing hooks. * - * 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. + * Same pixel behavior as v5 (verified 0/19200 NUC, 0/76800 gray, ghost = + * official frozen-ref artifact). Building/behavior switches: + * -DOFF_IMPL_230 / OFF_IMPL_41FE8 / OFF_IMPL_30 / OFF_IMPL_UPSCALE=1 + * keys 0/8/9 toggle the extra paths at runtime. */ #define _WIN32_WINNT 0x0601 #include #include #include +#include #include #include #include #include "mag160c/mag160c.h" +#include "mag160c/mag160c_render.h" #include "mag160c/mag160c_display.h" #include "mag160c_official_palette256.h" #include "mag160c_official_t2e.h" -#define 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" /* ===================================================================== @@ -109,6 +55,10 @@ static int g_ctrl_41fe8 = OFF_IMPL_41FE8 ? 1 : 0; /* 官方 dev+0x41fe8 */ static int g_ctrl_30 = OFF_IMPL_30; /* 官方 dev+0x30 渲染模式 */ static int g_ctrl_84 = 0; /* 官方 dev+0x84 */ static unsigned short *g_buf278; /* 官方 dev+0x278(当前会话 NULL) */ +static int g_dev24 = 5; /* 0x47944: window base (window scale) */ +static int g_dev4c_shift = 3; /* 0x4794c */ +static int g_dev47950 = 0; /* 0x47950: 0 static / 1 time-based */ +static int g_dev47948 = -50000; /* 0x47948: comp offset */ static int g_im41fec; /* 官方 dev+0x41fec */ static int g_im41ff0; /* 官方 dev+0x41ff0 */ static unsigned short g_sm_buf[((W / 2 + 2) * (H / 2 + 1) + 16) * 2]; @@ -119,70 +69,17 @@ 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; +static mag160c_render_t *g_render; -/* ---- 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 int g_fcount; /* rendered frames */ +static int g_global_frames; /* frames since start (incl. FFC window) */ static double g_fps; static double g_max_temp = -100; static double g_center_temp = -100; -static int g_probe_x = -1, g_probe_y = -1; static int g_max_x = -1, g_max_y = -1; +static int g_probe_x = -1, g_probe_y = -1; +static int g_manual_ffc; static HWND g_hwnd; static HFONT g_font; @@ -205,378 +102,25 @@ static int sendcmd(unsigned magic, unsigned param, int len) { 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)); +/* FFC 命令由 render 库通过回调发出(官方 0x6bb6b672) */ +static void ffc_cb(void *user, int param) { + (void)user; + sendcmd(0x6bb6b672, (unsigned)param, 8); + FILE *f = fopen("demo3_ffc.txt", "a"); + if (f) { + fprintf(f, "frame=%u FFC(%d)\n", g_global_frames, param); + fclose(f); } } -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). */ +/* ---------------- 附加环节:temporal (FUN_18001e920) ---------------- + * 官方 dev+0x41fe0 > 1 时启用(live=1,关闭);period==1: EMA 去噪。 */ static unsigned short g_tmp_hist[NPIX]; static unsigned short *g_tmp_ring = g_tmp_hist; static int g_tmp_cnt; -static int g_tmp_period = 1; /* dev+0x41f40+0x38 */ -static int g_tmp_enabled; /* dev+0x41f40+0x3c */ -static int g_tmp_mode = 1; /* dev+0x41fe0 */ +static int g_tmp_period = 1; +static int g_tmp_enabled; +static int g_tmp_mode = 1; static void temporal_apply(unsigned short *buf, int thr) { if (!g_tmp_enabled) return; @@ -601,10 +145,6 @@ static void temporal_apply(unsigned short *buf, int thr) { } /* ---------------- 附加环节:帧差调整 (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) @@ -621,13 +161,10 @@ static void frame_diff_adjust(unsigned short *out) { } } -/* ---------------- 附加环节:隔行平滑 (FUN_1800184f0, 0x41fe8 mode 1/3) ---- - * 偶数行每行取前 w/2+2 个样本 → 水平 2-tap 插值写回偶数行, - * 与下一行样本做 4-tap 对角平均写回奇数行(半分辨率降噪插值)。 */ +/* ---------------- 附加环节:隔行平滑 (FUN_1800184f0, 0x41fe8 mode 1/3) ---- */ static void interlace_smooth_2x(unsigned short *out) { int w = W, h = H; int sw = (w >> 1) + 2, sh = (h >> 1) + 1; - /* subsample: even rows, first sw columns (clamped) */ for (int y = 0; y < sh; ++y) { int sy = y * 2; if (sy >= h) sy = h - 1; @@ -635,7 +172,6 @@ static void interlace_smooth_2x(unsigned short *out) { 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; @@ -643,7 +179,6 @@ static void interlace_smooth_2x(unsigned short *out) { 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; @@ -656,9 +191,7 @@ static void interlace_smooth_2x(unsigned short *out) { } } -/* ---------------- 附加环节:隔行平滑 4x (FUN_180018660, mode 2/3/4) ----- - * 逐行翻译:每 4 行取 1 行、每行前 w/4+2 样本 → 4 相位输出, - * 每相位 2 个 u32(4 个 u16)水平/垂直加权插值。 */ +/* ---------------- 附加环节:隔行平滑 4x (FUN_180018660, mode 2/3/4) ----- */ static void interlace_smooth_4x(unsigned short *out) { int w = W, h = H; int sw = (w >> 2) + 2, sh = (h >> 2) + 1; @@ -676,18 +209,14 @@ static void interlace_smooth_4x(unsigned short *out) { 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 | @@ -696,12 +225,9 @@ static void interlace_smooth_4x(unsigned short *out) { } } -/* ---------------- 附加环节:manual gray 路径 (FUN_180017c40) ----------- - * 0x30>=1 且 0x84!=0 时:8x8 窗口 4x4 采样,高对比度区域做对比度 - * 增强累积(0x2a8 int 缓冲),最后累加到 gray(0x2a0)。 - * param_2 = 增强等级(官方传 0x30 模式 << 3 = 8/16/24/32)。逐行翻译。 */ +/* ---------------- 附加环节:manual gray 路径 (FUN_180017c40) ----------- */ static int g_enh_acc[NPIX]; /* 官方 dev+0x2a8 */ -static void gray_path_manual(const unsigned short *nuc, int enh) { +static void gray_path_manual(const unsigned short *nuc, unsigned char *gray160, int enh) { if (enh == 0) return; memset(g_enh_acc, 0, sizeof(g_enh_acc)); unsigned v18 = (unsigned)(g_dev24 * 1000) >> g_dev4c_shift; @@ -733,7 +259,6 @@ static void gray_path_manual(const unsigned short *nuc, int enh) { } } } - /* gray accumulate pass (0x2a0) */ for (int y = 0; y < H; ++y) { int rn = 4; if (y < 6) rn = (y >> 1) + 1; @@ -748,23 +273,18 @@ static void gray_path_manual(const unsigned short *nuc, int enh) { v = (int)((v >> 0x1f & 0x7ffff) + v) >> 0x13; else v = v / (cn * rn * 0x8000); - int gv = (int)g_gray160[i] + v; + int gv = (int)gray160[i] + v; if (gv < 0) gv = 0; if (gv > 255) gv = 255; - g_gray160[i] = (unsigned char)gv; + 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) */ + * 空间权重表官方被裁剪无法提取,用高斯核近似(σ=2.0)。 */ +static float g_gauss7[7]; +static float g_radial[24]; static void gray_path_auto_prep(void) { static int done; if (done) return; @@ -807,22 +327,19 @@ static void gray_path_auto(unsigned short *nuc) { } } -/* ---------------- 附加环节:Nx 升采样 (0x19740 模式推广) ----------- - * 2x 已逐像素验证(0/76800);4x/8x 用同一线性插值模式推广 - * (隔 n 采样 + (n-i)/n 权重,垂直 2-tap + 对角双线性,边缘外推)。 - * 当前设备固定 2x,仅供 OFF_IMPL_UPSCALE 测试。 */ +/* ---------------- 附加环节:Nx 升采样 (0x19740 模式推广) ------------- */ static unsigned char g_gray_big[1280 * 960]; -static void upscale_nx(int n, int *ow, int *oh) { +static void upscale_nx(const unsigned char *gray160, int n, int *ow, int *oh) { *ow = W * n; *oh = H * n; unsigned char *o = g_gray_big; - int lg = n == 2 ? 1 : (n == 4 ? 2 : 3); /* log2(n) */ + int lg = n == 2 ? 1 : (n == 4 ? 2 : 3); for (int y = 0; y < H - 1; ++y) { for (int x = 0; x < W - 1; ++x) { - unsigned a = 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]; + unsigned a = gray160[y * W + x]; + unsigned b = gray160[y * W + x + 1]; + unsigned c = gray160[(y + 1) * W + x]; + unsigned d = gray160[(y + 1) * W + x + 1]; for (int j = 0; j < n; ++j) { for (int i = 0; i < n; ++i) { unsigned hv = (a * (n - i) + b * i) >> lg; @@ -838,143 +355,93 @@ static void upscale_nx(int n, int *ow, int *oh) { } } } - /* 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]; + unsigned a = gray160[(H - 1) * W + x]; + unsigned b = gray160[(H - 1) * W + x + 1]; + unsigned u = gray160[(H - 2) * W + x]; + unsigned u2 = gray160[(H - 2) * W + x + 1]; for (int i = 0; i < n; ++i) { unsigned hv = (a * (n - i) + b * i) >> lg; unsigned uh = (u * (n - i) + u2 * i) >> lg; o[(H * n - 1) * (*ow) + x * n + i] = (unsigned char)((3 * hv >> 2) + (uh >> 2)); } } - /* 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]; + unsigned a = gray160[y * W + W - 1]; + unsigned u = gray160[(y + 1) * W + W - 1]; + unsigned l = gray160[y * W + W - 2]; + unsigned l2 = gray160[(y + 1) * W + W - 2]; for (int j = 0; j < n; ++j) { unsigned vv = (a * (n - j) + u * j) >> lg; unsigned lh = (l * (n - j) + l2 * j) >> lg; o[(y * n + j) * (*ow) + W * n - 1] = (unsigned char)((3 * vv >> 2) + (lh >> 2)); } } - /* 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 a = gray160[(H - 1) * W + W - 1]; + unsigned l = gray160[(H - 1) * W + W - 2]; + unsigned u = gray160[(H - 2) * W + W - 1]; unsigned v1 = (3 * a + l) >> 2; unsigned v2 = (3 * a + u) >> 2; o[(*oh - 1) * (*ow) + (*ow) - 1] = (unsigned char)((v1 + v2 + a) / 3); } } -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]; +/* ---------------- render 钩子 ---------------- + * post_nuc: 在 NUC+盲元之后、统计之前(官方 0xca30 路径顺序)。 + * post_gray: 在 gray160 生成之后、2x 之前。 */ + +static unsigned short g_nuc_saved[NPIX]; /* manual gray 路径需要 NUC 域 */ + +static void post_nuc_cb(void *user, unsigned short *nuc) { + (void)user; + int thr = (g_dev24 * 1000 >> 1) >> g_dev4c_shift; /* 0x15680 static */ + if (thr > 0xffff) thr = 0xffff; + if (g_tmp_mode > 1) temporal_apply(nuc, thr); +#if OFF_IMPL_230 + if (g_ctrl_230 != 0) + frame_diff_adjust(nuc); +#endif +#if OFF_IMPL_41FE8 + if (g_ctrl_41fe8 == 1 || g_ctrl_41fe8 == 3) + interlace_smooth_2x(nuc); + if (g_ctrl_41fe8 == 2 || g_ctrl_41fe8 == 3 || g_ctrl_41fe8 == 4) { + interlace_smooth_4x(nuc); + if (g_ctrl_41fe8 == 4) + interlace_smooth_4x(nuc); } +#endif +#if OFF_IMPL_30 + if (g_ctrl_30 >= 1 && g_ctrl_30 <= 4) { + if (g_ctrl_84 == 0) + gray_path_auto(nuc); + else + memcpy(g_nuc_saved, nuc, sizeof(g_nuc_saved)); + } +#endif } -static void 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); - } +static void post_gray_cb(void *user, unsigned char *gray160) { + (void)user; +#if OFF_IMPL_30 + if (g_ctrl_30 >= 1 && g_ctrl_30 <= 4 && g_ctrl_84 != 0) + gray_path_manual(g_nuc_saved, gray160, g_ctrl_30 << 3); +#else + (void)gray160; +#endif } -/* ---------------- palette + BMP ---------------- */ +/* ---------------- palette + BMP(render 输出 RGB24 → bottom-up BGR) ---- */ -static void render_bmp(void) { +static void render_bmp(const unsigned char *rgb) { unsigned char *px = g_bmp + 54; - /* 官方输出是 320x240:用 2x 升采样后的灰度上色(2-tap 平滑本身 - * 就抑制了颗粒感;官方 app 显示的也是这个 2x 缓冲)。 */ for (int y = 0; y < VH; ++y) { + const unsigned char *src = rgb + (size_t)y * VW * 3; + unsigned char *dst = px + (size_t)(VH - 1 - y) * VW * 3; for (int x = 0; x < VW; ++x) { - 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; + dst[x * 3 + 0] = src[x * 3 + 2]; + dst[x * 3 + 1] = src[x * 3 + 1]; + dst[x * 3 + 2] = src[x * 3 + 0]; } } if (g_max_x >= 0) { @@ -992,6 +459,24 @@ static void render_bmp(void) { } } +static double counts_to_c(double v) { + int64_t x = (int64_t)v * 3 - 5797; + if (x < 0) x = 0; + int lo = 0, hi = 645; + while (lo < hi) { + int mid = (lo + hi + 1) >> 1; + if (mag160c_official_t2e[mid] <= x) lo = mid; + else hi = mid - 1; + } + int i = lo; + if (i > 644) i = 644; + int64_t diff = x - mag160c_official_t2e[i]; + int64_t t2 = mag160c_official_t2e[i + 1] - mag160c_official_t2e[i]; + int64_t slope = t2 ? (0x1000000 + t2 / 2) / t2 : 0; + int64_t temp = ((slope * diff) >> 12) + ((int64_t)i << 12) - 0x249f0; + return temp / 1000.0; +} + static LRESULT CALLBACK wndproc(HWND hw, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_PAINT: { @@ -1017,8 +502,10 @@ static LRESULT CALLBACK wndproc(HWND hw, UINT msg, WPARAM wp, LPARAM lp) { 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); + int32_t tmc = 0; + mag160c_render_probe_temp(g_render, (uint32_t)g_probe_x, (uint32_t)g_probe_y, &tmc); + snprintf(line, sizeof(line), "probe : (%d,%d) %.2f C", g_probe_x, g_probe_y, + tmc / 1000.0); TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24; } if (g_max_x >= 0) { @@ -1027,10 +514,13 @@ static LRESULT CALLBACK wndproc(HWND hw, UINT msg, WPARAM wp, LPARAM lp) { } 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)"); + snprintf(line, sizeof(line), "pipeline: mag160c_render (official full)"); 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); + int lo, hi, fmin, fmax, mean, std; + mag160c_render_get_stats(g_render, &lo, &hi, &fmin, &fmax, &mean, &std); + snprintf(line, sizeof(line), "shutter: %d sel: %d win: [%d,%d]", + mag160c_render_get_shutter(g_render), + mag160c_render_get_endpoint(g_render), lo, hi); TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24; EndPaint(hw, &ps); break; @@ -1075,7 +565,6 @@ static LRESULT CALLBACK wndproc(HWND hw, UINT msg, WPARAM wp, LPARAM lp) { 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; @@ -1105,14 +594,27 @@ static LRESULT CALLBACK wndproc(HWND hw, UINT msg, WPARAM wp, LPARAM 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; - } + + /* render 管线初始化(DDT 由 render 库加载) */ + mag160c_render_cfg_t cfg; + memset(&cfg, 0, sizeof(cfg)); + cfg.width = W; + cfg.height = H; + cfg.ddt_path = OFF_DDT; + cfg.ffc_period = 1800; + cfg.ffc_drift = 250; + cfg.startup_force_ffc = 1; + cfg.warm_frames = 20; + cfg.cdf_pivot_75 = 1; + cfg.ffc_cb = ffc_cb; + cfg.post_nuc_cb = post_nuc_cb; + cfg.post_gray_cb = post_gray_cb; + mag160c_error_t rerr = mag160c_render_init(&g_render, &cfg); + if (rerr != MAG160C_OK) { + MessageBoxA(NULL, "render init failed (need mag160c_official.ddt)", "MAG160C Demo", MB_ICONERROR); + return 1; } + int ok = 0; for (int attempt = 0; attempt < 4 && !ok; ++attempt) { if (attempt > 0) { @@ -1145,17 +647,18 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int show) { } if (!ok) { MessageBoxA(NULL, "camera init failed (retried 4x)", "MAG160C Demo", MB_ICONERROR); + mag160c_render_destroy(g_render); return 1; } WNDCLASSA wc = {0}; wc.lpfnWndProc = wndproc; wc.hInstance = inst; - wc.lpszClassName = "Mag160cDemoV5"; + wc.lpszClassName = "Mag160cDemoV6"; wc.hCursor = LoadCursor(NULL, IDC_CROSS); wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); RegisterClassA(&wc); - g_hwnd = CreateWindowA("Mag160cDemoV5", "MAG160C Thermal Demo v5 (official pipeline)", + g_hwnd = CreateWindowA("Mag160cDemoV6", "MAG160C Thermal Demo v6 (render lib)", WS_OVERLAPPEDWINDOW, 60, 40, 1000, 620, NULL, NULL, inst, NULL); g_font = CreateFontA(18, 0, 0, 0, FW_NORMAL, 0, 0, 0, ANSI_CHARSET, @@ -1183,19 +686,14 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int show) { 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"); + static uint8_t rgb[VW * VH * 3]; + static uint8_t full[28 + 40000]; unsigned prev2 = 0xffffffff; DWORD t0 = GetTickCount(); DWORD lfps_t = t0; unsigned lfps_c = 0; DWORD last_frame_t = t0; DWORD last_reset_t = t0; - int ref_reset = 0; - int skipped = 0; for (;;) { MSG msg; @@ -1222,204 +720,44 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int show) { ((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; + 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; + /* 组装完整帧(28B 头 + 38400 像素 + 28B 尾,尾部含快门字) */ + memcpy(full, hdr, 28); + memcpy(full + 28, g_frame, 38400); + if (xfer > 38400) + memcpy(full + 28 + 38400, g_frame + 38400, (size_t)(xfer - 38400)); if (g_manual_ffc) { g_manual_ffc = 0; - 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); } + mag160c_render_request_ffc(g_render); } - /* 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)); + mag160c_error_t rc = mag160c_render_frame(g_render, full, 1, rgb); + if (rc != MAG160C_OK) continue; /* startup/FFC window: frozen */ 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; + int hot = -1; for (int i = 0; i < NPIX; ++i) { - if (g_nuc[i] > hotv && g_nuc[i] != 0) { hotv = g_nuc[i]; hot = i; } + uint32_t v = mag160c_render_get_nuc(g_render, (uint32_t)i); + if (v > hotv) { hotv = v; hot = i; } } if (hot >= 0 && hotv) { g_max_x = hot % W; g_max_y = hot / W; g_max_temp = counts_to_c((double)hotv); } - g_center_temp = counts_to_c((double)g_stat_mean); - render_bmp(); + int lo, hi, fmin, fmax, mean, std; + mag160c_render_get_stats(g_render, &lo, &hi, &fmin, &fmax, &mean, &std); + g_center_temp = counts_to_c((double)mean); + render_bmp(rgb); - 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); @@ -1427,8 +765,9 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int show) { 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); + "MAG160C v6 render-lib - frame %u shutter %d sel %d win [%d,%d]", + g_global_frames, mag160c_render_get_shutter(g_render), + mag160c_render_get_endpoint(g_render), lo, hi); SetWindowTextA(g_hwnd, g_title); if (g_fcount % 30 == 0 && g_fcount / 30 < 40) { char pth[MAX_PATH]; @@ -1436,33 +775,15 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int show) { 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); + mag160c_render_destroy(g_render); libusb_close(g_h); libusb_exit(g_ctx); return 0;