/* mag160c_render.c - official pipeline, platform-independent. * Migrated from csdk/tools/mag160c_demo3.c (pixel-verified vs * CoreSDKLib.dll). Feed it USB frames; get 320x240 RGB24. */ #include "mag160c/mag160c_render.h" #include "mag160c_official_palette256.h" #include "mag160c_official_t2e.h" #include #include #include #include #define MAG_T2E_OFFSET 0x249f0 #define TEMP_C 5797 #define OFF_N0 4 #define OFF_N1 5 #define OFF_N2 4 struct mag160c_render_t { uint16_t w, h; int npix; /* DDT state */ int ep_count, nsegs, c5c, c60; int dev24, dev4c_shift, dev48, dev8c, dev47950, dev47948; int shift; int ep_temps[64]; int blind_cnt[64]; short *ep_thr[64]; unsigned short *ep_gain[64]; unsigned int *ep_blind[64]; int sel; short *thr_work; unsigned short *gain_work; unsigned int *blind_work; int blind_count; /* frame/FFC state */ 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; unsigned short *nuc; unsigned char *gray160, *gray320; /* render state */ unsigned int hist1024[1024]; unsigned int cdf[1024]; unsigned char lut[1024]; int win_lo, win_hi; int stat_min, stat_max, stat_mean, stat_std; int center_prev, mean_prev; /* 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; }; 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; } /* ---------------- DDT loading (official 0x18000f130 v3) ---------------- */ static int load_ddt(mag160c_render_t *r, const char *path) { FILE *f = fopen(path, "rb"); if (!f) return 0; unsigned char hdr[256]; if (fread(hdr, 1, 0x100, f) < 0x98) { fclose(f); return 0; } unsigned magic = *(unsigned *)(hdr + 0); unsigned w = *(unsigned *)(hdr + 4), hh = *(unsigned *)(hdr + 8); if (magic != 0x5aa50003 && magic != 0x5aa50004) { fclose(f); return 0; } if (w != r->w || hh != r->h) { fclose(f); return 0; } r->ep_count = *(int *)(hdr + 12); r->nsegs = *(int *)(hdr + 16); if (r->ep_count < 2 || r->ep_count > 64 || r->nsegs < 1 || r->nsegs > 8) { fclose(f); return 0; } int v4 = (magic == 0x5aa50004); r->dev24 = *(int *)(hdr + 20); r->dev4c_shift = 3; if (!v4) { r->c5c = *(int *)(hdr + 28); r->c60 = *(int *)(hdr + 32); if (r->c5c > 0x10000) r->c5c = 0; } else { r->c5c = *(int *)(hdr + 0x2c + 0x120); r->c60 = *(int *)(hdr + 0x30 + 0x120); } if (r->c5c & 1) r->c5c &= ~1; int arro = v4 ? 0x120 + 36 : 36; if (fseek(f, arro, SEEK_SET)) { fclose(f); return 0; } if (fread(r->ep_temps, 4, r->ep_count, f) != (size_t)r->ep_count) { fclose(f); return 0; } unsigned tmp[64]; if (fread(tmp, 4, r->ep_count, f) != (size_t)r->ep_count) { fclose(f); return 0; } if (fread(tmp, 4, r->ep_count, f) != (size_t)r->ep_count) { fclose(f); return 0; } if (fread(r->blind_cnt, 4, r->ep_count - 1, f) != (size_t)(r->ep_count - 1)) { fclose(f); return 0; } r->blind_cnt[r->ep_count - 1] = 0; for (int i = 0; i < r->ep_count; ++i) if (r->blind_cnt[i] > 4096) r->blind_cnt[i] = 4096; size_t thr_sz = (size_t)(r->nsegs - 1) * r->npix + r->c5c / 2; size_t gain_sz = (size_t)r->nsegs * r->npix * 2; for (int e = 0; e < r->ep_count; ++e) { r->ep_thr[e] = malloc(thr_sz * 2); r->ep_gain[e] = malloc(gain_sz * 2); r->ep_blind[e] = malloc((size_t)r->blind_cnt[e] * 40); if (!r->ep_thr[e] || !r->ep_gain[e] || (r->blind_cnt[e] && !r->ep_blind[e])) { fclose(f); return 0; } if (fread(r->ep_thr[e], 2, thr_sz, f) != thr_sz) { fclose(f); return 0; } if (fread(r->ep_gain[e], 2, gain_sz, f) != gain_sz) { fclose(f); return 0; } } for (int e = 0; e < r->ep_count - 1; ++e) { if (r->blind_cnt[e] && fread(r->ep_blind[e], 40, r->blind_cnt[e], f) != (size_t)r->blind_cnt[e]) { fclose(f); return 0; } } fclose(f); r->shift = 12; return 1; } static int select_endpoint(mag160c_render_t *r, int shutter) { int sel = 0; if (r->ep_count != 2) { while (sel < r->ep_count - 2 && shutter > r->ep_temps[sel + 1]) sel++; } return sel; } 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(mag160c_render_t *r, int shutter) { r->sel = select_endpoint(r, shutter); int sel = r->sel; int t = ((shutter - r->ep_temps[sel]) << 12) / (r->ep_temps[sel + 1] - r->ep_temps[sel]); if (t > 0x3fff) t = 0x3fff; if (t < -0x3fff) t = -0x3fff; interp_i16(r->ep_thr[sel], r->ep_thr[sel + 1], r->thr_work, (r->nsegs - 1) * r->npix + r->c5c / 2, t); interp_u16(r->ep_gain[sel], r->ep_gain[sel + 1], r->gain_work, r->nsegs * r->npix * 2, t); r->blind_work = r->ep_blind[sel]; r->blind_count = r->blind_cnt[sel]; } /* ---------------- ref smoother (mode = 4-frame mean) ---------------- */ static void ref_push(mag160c_render_t *r, const unsigned short *frame) { if (r->ref_cnt == 0) { for (int i = 0; i < r->npix; ++i) r->ref_acc[i] = frame[i]; } else { for (int i = 0; i < r->npix; ++i) r->ref_acc[i] += frame[i]; } r->ref_cnt++; if (r->ref_cnt >= OFF_N0) { for (int i = 0; i < r->npix; ++i) r->ref[i] = (unsigned short)(r->ref_acc[i] >> 2); r->ref_cnt = 0; r->has_ref = 1; } } /* ---------------- NUC + blind (0x180017200 + 0x180017330) ---------------- */ static void nuc_and_blind(mag160c_render_t *r, const unsigned short *f20, unsigned short *out) { const short *thr = r->thr_work; const unsigned short *gg = r->gain_work; for (int i = 0; i < r->npix; ++i) { int d2 = ((int)f20[i] - (int)r->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 * r->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; } if (r->blind_work && r->blind_count > 0) { for (int k = 0; k < r->blind_count; ++k) { const unsigned int *rec = r->blind_work + k * 10; unsigned target = rec[0], typ = rec[1]; const unsigned int *ng = rec + 2; if (target >= (unsigned)r->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 stats_window(mag160c_render_t *r, const unsigned short *nuc) { unsigned long long ssum = 0, s2sum = 0; unsigned mn = 0xffff, mx = 0; for (int i = 0; i < r->npix; ++i) { unsigned v = nuc[i]; ssum += v; s2sum += (unsigned long long)v * v; if (v < mn) mn = v; if (v > mx) mx = v; } r->stat_min = (int)mn; r->stat_max = (int)mx; r->stat_mean = (int)(ssum / r->npix); double dmean = (double)ssum / r->npix; double var = (double)s2sum / r->npix - dmean * dmean; r->stat_std = (int)sqrt(var > 0 ? var : 0); if (mn == mx) { r->win_lo = r->win_hi = (int)mn; memset(r->lut, 0x80, sizeof(r->lut)); memset(r->gray160, 0x80, r->npix); return; } int base = (r->dev24 * 1000) >> r->dev4c_shift; if (base < 0x80) base = 0x80; int half = base >> 1; int lo = r->stat_mean - half; if ((unsigned)lo > (unsigned)r->stat_min) lo = r->stat_min; if (lo < 0) lo = 0; int hi = r->stat_mean + half; if ((unsigned)hi < (unsigned)r->stat_max) hi = r->stat_max; if (hi > 65535) hi = 65535; r->win_lo = lo; r->win_hi = hi; if (r->win_hi <= r->win_lo) r->win_hi = r->win_lo + 1; } /* ---------------- LUT1024 rebuild (0x180011950 + 0x180011ee0) ---------------- */ static void lut_rebuild(mag160c_render_t *r, const unsigned short *nuc) { int span = r->win_hi - r->win_lo; unsigned S = 0xFFC00000u / (unsigned)span; memset(r->hist1024, 0, sizeof(r->hist1024)); for (int i = 0; i < r->npix; ++i) { unsigned idx = ((unsigned)nuc[i] - (unsigned)r->win_lo) * S >> 0x16; if (idx < 1024) r->hist1024[idx]++; } for (int i = 0; i < 1023; i += 3) { r->hist1024[i] = (r->hist1024[i + 1] + r->hist1024[i]) >> 1; r->hist1024[i + 1] = (r->hist1024[i + 2] + r->hist1024[i + 1]) >> 1; r->hist1024[i + 2] = (r->hist1024[i + 3] + r->hist1024[i + 2]) >> 1; } unsigned total = 0; for (int i = 0; i < 1024; ++i) total += r->hist1024[i]; unsigned u21 = total >> 12; if (u21 == 0) u21 = 1; int lres = ((r->stat_mean - r->win_lo) * 0x3ff) / span; if (lres < 0) lres = 0; if (lres > 1023) lres = 1023; if (r->cdf_pivot_75) { unsigned acc = 0; int k = 0; while (k < 1024) { acc += r->hist1024[k]; if (acc >= (total * 3 >> 2)) break; k++; } if (k < 1024 && k > lres) lres = k; } unsigned u11 = 0, u13 = 0; for (int k = lres; k >= 0; --k) { u11 += r->hist1024[k]; if (u21 <= u11) { u13 += 0x100; u11 = 0; } r->cdf[k] = ((0x10000 / u21) * u11 >> 8) + u13; } u11 = 0; u13 = 0; for (int k = lres + 1; k < 1024; ++k) { u11 += r->hist1024[k]; if (u11 < u21 * 8) { if (u21 <= u11) { u13 += 0x100; u11 = 0; } } else { u13 += 0x200; u11 = 0; } r->cdf[k] = ((0x10000 / u21) * u11 >> 8) + u13; } 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 = (r->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; } unsigned u5 = r->cdf[0]; if (u5 != 0) { unsigned u21v = (unsigned)(iv7 * 0x10 + 0x10); for (int k = lres; k >= 0; --k) { unsigned u12 = (unsigned)(iv7 * 0x10 + 0x10) - (r->cdf[k] * ((iv7 * 0x40000u + 0x40000u) / u5) >> 0xe); if (curve[k] < u21v - u12) u12 = u21v - curve[k]; r->lut[k] = (unsigned char)(u12 >> 6); u21v = u12; } } u5 = r->cdf[1023]; if (u5 != 0) { unsigned u21v = (unsigned)(iv7 * 0x10); for (int k = lres + 1; k < 1024; ++k) { unsigned u12 = (r->cdf[k] * ((0xffc0000u - iv7 * 0x40000u) / u5) >> 0xe) + (unsigned)(iv7 * 0x10); if (curve[k] < u12 - u21v) u12 = curve[k] + u21v; r->lut[k] = (unsigned char)(u12 >> 6); u21v = u12; } } int iv4 = iv7; unsigned bfmin = ((unsigned)r->stat_min - (unsigned)r->win_lo) * iv22 >> 0x16; unsigned bfmax = ((unsigned)r->stat_max - (unsigned)r->win_lo) * iv22 >> 0x16; if (bfmin < 1024 && bfmax < 1024 && r->lut[bfmin] == 0 && r->lut[bfmax] != 0xff) { iv4 = iv10; iv26 = iv7; } iv10 = iv4; } int dm = r->stat_mean - r->mean_prev; if (dm < 0) dm = -dm; r->mean_prev = r->stat_mean; int iv18 = iv10 + ((dm & 0xffff) << r->dev4c_shift) * 2 + r->stat_mean; if (iv18 < 0x9c4) { if (iv18 < 100) iv18 = 100; iv7 = (iv18 * iv7 + r->center_prev * 500) / (iv18 + 500); r->center_prev = iv7; unsigned u5 = r->cdf[0]; if (u5 != 0) { unsigned u21v = (unsigned)(iv7 * 0x10 + 0x10); for (int k = lres; k >= 0; --k) { unsigned u12 = (unsigned)(iv7 * 0x10 + 0x10) - (r->cdf[k] * ((iv7 * 0x40000u + 0x40000u) / u5) >> 0xe); if (curve[k] < u21v - u12) u12 = u21v - curve[k]; r->lut[k] = (unsigned char)(u12 >> 6); u21v = u12; } } u5 = r->cdf[1023]; if (u5 != 0) { unsigned u21v = (unsigned)(iv7 * 0x10); for (int k = lres + 1; k < 1024; ++k) { unsigned u12 = (r->cdf[k] * ((0xffc0000u - iv7 * 0x40000u) / u5) >> 0xe) + (unsigned)(iv7 * 0x10); if (curve[k] < u12 - u21v) u12 = curve[k] + u21v; r->lut[k] = (unsigned char)(u12 >> 6); u21v = u12; } } } } /* ---------------- gray + 2x upscale (0x180019740) ---------------- */ static void gray_map(mag160c_render_t *r, const unsigned short *nuc) { unsigned S = 0xFFC00000u / (unsigned)(r->win_hi - r->win_lo); for (int i = 0; i < r->npix; ++i) { unsigned v = nuc[i]; unsigned idx; if (v <= (unsigned)r->win_lo) idx = 0; else if (v >= (unsigned)r->win_hi) idx = 1023; else idx = (v - (unsigned)r->win_lo) * S >> 0x16; if (idx > 1023) idx = 1023; r->gray160[i] = r->lut[idx]; } } static void upscale2x(mag160c_render_t *r) { const unsigned char *s = r->gray160; unsigned char *o = r->gray320; int W = r->w, H = r->h, W2 = W * 2; for (int y = 0; y < H - 1; ++y) { const unsigned char *r0 = s + y * W, *r1 = r0 + W; unsigned char *o0 = o + y * W2 * 2, *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); } 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); 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); } { int y = H - 1; const unsigned char *r0 = s + y * W, *r1 = r0 - W; unsigned char *o0 = o + y * W2 * 2, *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); } } /* ---------------- FFC state machine (0x180009ee0) ---------------- * Returns: 0 = render this frame * 1 = hidden frame (startup/rebuild window), freeze display * 2 = ref collection frame (type-1 window): caller must push * the decoded pixels through ref_push before freezing. */ static int ffc_step(mag160c_render_t *r, int shutter) { if (r->ffc_idx < 0) { if (r->ffc_cb) r->ffc_cb(r->ffc_user, 0); r->ffc_idx = 0; r->shutter_at_ffc = shutter; } r->ffc_idx++; int idx = r->ffc_idx; if (idx <= OFF_N1) { if (idx == 1) rebuild_tables(r, r->shutter_at_ffc); return 1; } if (idx <= OFF_N0 + OFF_N1) { if (idx == OFF_N0 + OFF_N1) { if (r->ffc_cb) r->ffc_cb(r->ffc_user, 1); } if (idx == OFF_N1 + 1) { r->ref_cnt = 0; r->has_ref = 0; } return 2; /* ref collection window */ } if (idx <= OFF_N0 + OFF_N2 + OFF_N1) return 1; /* normal frame: FFC condition */ { int drift = shutter - r->shutter_at_ffc; 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; } if (idx >= OFF_N0 + r->ffc_period) do_ffc = 1; else if (idx >= cool + 30 && drift > r->ffc_drift) do_ffc = 1; if (do_ffc) { if (r->ffc_cb) r->ffc_cb(r->ffc_user, 0); r->ffc_idx = 0; r->shutter_at_ffc = shutter; return 1; } } return 0; } /* ---------------- public API ---------------- */ mag160c_error_t mag160c_render_init(mag160c_render_t **out, const mag160c_render_cfg_t *cfg) { if (!out || !cfg || !cfg->ddt_path) return MAG160C_ERR_INVALID_ARGUMENT; mag160c_render_t *r = calloc(1, sizeof(*r)); if (!r) return MAG160C_ERR_NO_MEMORY; r->w = cfg->width ? cfg->width : 160; r->h = cfg->height ? cfg->height : 120; 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; r->cdf_pivot_75 = cfg->cdf_pivot_75; r->force75 = cfg->startup_force_ffc; r->dev24 = 5; r->dev4c_shift = 3; r->dev48 = 0; r->dev47950 = 0; r->dev47948 = -50000; r->ffc_idx = -1; r->center_prev = 341; r->mean_prev = 0; r->thr_work = malloc((r->npix * 2 + 4096) * sizeof(short)); r->gain_work = malloc((size_t)r->npix * 6 * sizeof(unsigned short)); r->ref = malloc((size_t)r->npix * 2); r->ref_acc = malloc((size_t)r->npix * 4); r->nuc = malloc((size_t)r->npix * 2); r->gray160 = malloc((size_t)r->npix); r->gray320 = malloc((size_t)r->npix * 4); if (!r->thr_work || !r->gain_work || !r->ref || !r->ref_acc || !r->nuc || !r->gray160 || !r->gray320) { mag160c_render_destroy(r); return MAG160C_ERR_NO_MEMORY; } if (!load_ddt(r, cfg->ddt_path)) { mag160c_render_destroy(r); return MAG160C_ERR_NOT_INITIALIZED; } *out = r; return MAG160C_OK; } mag160c_error_t mag160c_render_frame(mag160c_render_t *r, const uint8_t *frame, int has_hdr, uint8_t *out_rgb) { if (!r || !frame || !out_rgb) return MAG160C_ERR_INVALID_ARGUMENT; r->global_frames++; if (r->global_frames < r->warm_frames) return MAG160C_ERR_NOT_READY; int shutter = r->shutter; if (has_hdr) { unsigned len = (unsigned)frame[8] | ((unsigned)frame[9] << 8) | ((unsigned)frame[10] << 16) | ((unsigned)frame[11] << 24); if (len == (unsigned)(r->npix * 2)) { const uint8_t *tail = frame + 0x1c + len; shutter = (int)((unsigned)tail[8] | ((unsigned)tail[9] << 8) | ((unsigned)tail[10] << 16) | ((unsigned)tail[11] << 24)); } } r->shutter = shutter; int st = ffc_step(r, shutter); if (st != 0) { if (st == 2) { /* ref window: push decoded pixels (type-1 calibration frames) */ const uint8_t *px = has_hdr ? frame + 0x1c : frame; for (int i = 0; i < r->npix; ++i) r->nuc[i] = (unsigned short)(px[i * 2] | ((unsigned)px[i * 2 + 1] << 8)); ref_push(r, r->nuc); } return MAG160C_ERR_NOT_READY; } /* decode pixels (frame + 0x1c when header present) */ const uint8_t *px = has_hdr ? frame + 0x1c : frame; unsigned short *live = r->nuc; for (int i = 0; i < r->npix; ++i) live[i] = (unsigned short)(px[i * 2] | ((unsigned)px[i * 2 + 1] << 8)); 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) { unsigned char gv = r->gray320[i]; out_rgb[i * 3 + 0] = mag160c_official_palette256[gv][0]; out_rgb[i * 3 + 1] = mag160c_official_palette256[gv][1]; out_rgb[i * 3 + 2] = mag160c_official_palette256[gv][2]; } return MAG160C_OK; } void mag160c_render_set_shutter(mag160c_render_t *r, int shutter) { if (r) r->shutter = shutter; } void mag160c_render_get_stats(mag160c_render_t *r, int *win_lo, int *win_hi, int *fmin, int *fmax, int *mean, int *stddev) { if (!r) return; if (win_lo) *win_lo = r->win_lo; if (win_hi) *win_hi = r->win_hi; if (fmin) *fmin = r->stat_min; if (fmax) *fmax = r->stat_max; if (mean) *mean = r->stat_mean; if (stddev) *stddev = r->stat_std; } int mag160c_render_get_shutter(mag160c_render_t *r) { return r ? r->shutter : 0; } int mag160c_render_get_endpoint(mag160c_render_t *r) { return r ? r->sel : 0; } int mag160c_render_get_frame_index(mag160c_render_t *r) { return r ? r->ffc_idx : 0; } int mag160c_render_has_reference(mag160c_render_t *r) { return r ? r->has_ref : 0; } mag160c_error_t mag160c_render_probe_temp(mag160c_render_t *r, uint32_t x, uint32_t y, int32_t *out_temp_mc) { if (!r || !out_temp_mc || x >= r->w || y >= r->h) return MAG160C_ERR_INVALID_ARGUMENT; *out_temp_mc = counts_to_temp_mc(r->nuc[y * r->w + x]); return MAG160C_OK; } void mag160c_render_destroy(mag160c_render_t *r) { if (!r) return; for (int e = 0; e < r->ep_count; ++e) { free(r->ep_thr[e]); free(r->ep_gain[e]); free(r->ep_blind[e]); } free(r->thr_work); free(r->gain_work); free(r->ref); free(r->ref_acc); free(r->nuc); free(r->gray160); 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]; }