android: 统一APP工程骨架 + 官方渲染管线Kotlin移植(与C参考逐字节一致)
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
.gradle/
|
||||
build/
|
||||
local.properties
|
||||
.idea/
|
||||
*.iml
|
||||
captures/
|
||||
@@ -0,0 +1,52 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.mag160c.thermal"
|
||||
compileSdk = 36
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.mag160c.thermal"
|
||||
minSdk = 26
|
||||
targetSdk = 36
|
||||
versionCode = 1
|
||||
versionName = "1.0.0"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
packaging {
|
||||
resources.excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(platform(libs.compose.bom))
|
||||
implementation(libs.compose.ui)
|
||||
implementation(libs.compose.ui.graphics)
|
||||
implementation(libs.compose.ui.tooling.preview)
|
||||
implementation(libs.compose.material3)
|
||||
implementation(libs.activity.compose)
|
||||
implementation(libs.core.ktx)
|
||||
implementation(libs.lifecycle.runtime.ktx)
|
||||
debugImplementation(libs.compose.ui.tooling)
|
||||
testImplementation(libs.junit)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-feature android:name="android.hardware.usb.host" android:required="true" />
|
||||
|
||||
<application
|
||||
android:label="MAG160C"
|
||||
android:theme="@android:style/Theme.Material.Light.NoActionBar"
|
||||
android:supportsRtl="true"
|
||||
android:allowBackup="false">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|uiMode"
|
||||
android:resizeableActivity="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.mag160c.thermal
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
setContent { App() }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun App() {
|
||||
Text("MAG160C build check")
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.mag160c.thermal.core
|
||||
|
||||
/** Little-endian byte helpers used by the recovered vendor parsers. */
|
||||
object ByteReader {
|
||||
fun rd32(b: ByteArray, off: Int): Int =
|
||||
(b[off].toInt() and 0xFF) or
|
||||
((b[off + 1].toInt() and 0xFF) shl 8) or
|
||||
((b[off + 2].toInt() and 0xFF) shl 16) or
|
||||
((b[off + 3].toInt() and 0xFF) shl 24)
|
||||
|
||||
fun rd16(b: ByteArray, off: Int): Int =
|
||||
(b[off].toInt() and 0xFF) or ((b[off + 1].toInt() and 0xFF) shl 8)
|
||||
|
||||
/** Decode `count` little-endian uint16 values into an IntArray. */
|
||||
fun decodeU16(b: ByteArray, off: Int, count: Int, out: IntArray, outOff: Int = 0) {
|
||||
for (i in 0 until count) {
|
||||
out[outOff + i] = rd16(b, off + i * 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.mag160c.thermal.core
|
||||
|
||||
/**
|
||||
* Streaming frame assembler, ported from csdk/src/mag160c_frame.c (vendor
|
||||
* libmagcore reader thread). Feed every bulk read from EP 0x81 into
|
||||
* [push]; when a complete valid frame is buffered it is copied to
|
||||
* [outFrame] and the returned length is > 0.
|
||||
*/
|
||||
class FrameStream(maxFrameLen: Int) {
|
||||
private val cap = maxFrameLen * 2 + 0x470
|
||||
private val buf = ByteArray(cap)
|
||||
private var len = 0
|
||||
private var aligned = false
|
||||
|
||||
/**
|
||||
* Append bulk-read data. Returns the total frame size when a complete
|
||||
* frame has been copied into [outFrame] (capacity must be >= 0x38 + len),
|
||||
* otherwise 0.
|
||||
*/
|
||||
fun push(data: ByteArray, size: Int, outFrame: ByteArray): Int {
|
||||
if (cap == 0 || size > cap - len) {
|
||||
len = 0
|
||||
aligned = false
|
||||
}
|
||||
if (size > cap - len) {
|
||||
len = 0
|
||||
}
|
||||
System.arraycopy(data, 0, buf, len, size)
|
||||
len += size
|
||||
|
||||
if (!aligned) {
|
||||
alignToMarker()
|
||||
if (!aligned) return 0
|
||||
}
|
||||
|
||||
if (len < FRAME_OVERHEAD) return 0
|
||||
val dataLen = ByteReader.rd32(buf, 8)
|
||||
if (dataLen < 0 || dataLen > cap - FRAME_OVERHEAD) {
|
||||
len = 0 // bogus length; resync
|
||||
return 0
|
||||
}
|
||||
if (len < FRAME_OVERHEAD + dataLen) return 0
|
||||
|
||||
if (!isValidFrame(buf, len)) {
|
||||
len = 0
|
||||
aligned = false
|
||||
return 0
|
||||
}
|
||||
val total = FRAME_OVERHEAD + dataLen
|
||||
System.arraycopy(buf, 0, outFrame, 0, total)
|
||||
|
||||
// consume frame
|
||||
System.arraycopy(buf, total, buf, 0, len - total)
|
||||
len -= total
|
||||
if (len == 0) aligned = false
|
||||
return total
|
||||
}
|
||||
|
||||
private fun alignToMarker() {
|
||||
var i = 0
|
||||
while (i + 4 <= len) {
|
||||
if (ByteReader.rd32(buf, i) == FRAME_MARKER) break
|
||||
i += 4
|
||||
}
|
||||
if (i + 4 > len) {
|
||||
len = 0
|
||||
return
|
||||
}
|
||||
if (i != 0) {
|
||||
System.arraycopy(buf, i, buf, 0, len - i)
|
||||
len -= i
|
||||
}
|
||||
aligned = true
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val FRAME_MARKER = 0x1BB1B11B
|
||||
const val FRAME_TRAILING_MARKER = 0x1BB1B11C
|
||||
const val DATA_OFFSET = 0x1C
|
||||
const val FRAME_OVERHEAD = 0x38
|
||||
|
||||
/** Validate a buffered frame: marker, type <= 1, length and trailer. */
|
||||
fun isValidFrame(buf: ByteArray, size: Int): Boolean {
|
||||
if (size < DATA_OFFSET + 4) return false
|
||||
if (ByteReader.rd32(buf, 0) != FRAME_MARKER) return false
|
||||
if (ByteReader.rd32(buf, 12) > 1) return false
|
||||
val dataLen = ByteReader.rd32(buf, 8)
|
||||
if (size < FRAME_OVERHEAD + dataLen) return false
|
||||
return ByteReader.rd32(buf, DATA_OFFSET + dataLen) == FRAME_TRAILING_MARKER
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
package com.mag160c.thermal.core
|
||||
|
||||
/** Generated from csdk C headers by analysis/gen_kotlin_tables.py. DO NOT EDIT. */
|
||||
object OfficialTables {
|
||||
/** Official default palette: 256 gray levels -> ARGB int (from CoreSDKLib dev+0xb18, B,G,R order swapped). */
|
||||
val PALETTE256_ARGB = intArrayOf(
|
||||
-16777216, -16777211, -16777206, -16777201, -16777195, -16777190,
|
||||
-16777185, -16777179, -16777174, -16777169, -16777163, -16777158,
|
||||
-16777153, -16777147, -16777142, -16777137, -16777131, -16777126,
|
||||
-16777121, -16777115, -16777110, -16777105, -16777099, -16646027,
|
||||
-16449418, -16318346, -16121737, -15925129, -15794056, -15597448,
|
||||
-15400839, -15269767, -15073158, -14876550, -14745477, -14548869,
|
||||
-14352260, -14221188, -14024579, -13827971, -13696898, -13500290,
|
||||
-13369217, -13172609, -12976000, -12844928, -12648319, -12451710,
|
||||
-12320638, -12124029, -11927421, -11796348, -11599740, -11403131,
|
||||
-11272059, -11075450, -10878842, -10747769, -10551161, -10420088,
|
||||
-10223480, -10026871, -9895799, -9699190, -9502582, -9371509,
|
||||
-9174901, -8978292, -8847220, -8650611, -8454002, -8322930,
|
||||
-8126321, -7929713, -7798640, -7602032, -7470959, -7274351,
|
||||
-7077742, -6946670, -6750061, -6553453, -6422380, -6225772,
|
||||
-6029163, -5898091, -5701482, -5504874, -5373801, -5177193,
|
||||
-4980584, -4849512, -4652903, -4456294, -4390504, -4324458,
|
||||
-4192876, -4127086, -4061040, -3929458, -3863412, -3797622,
|
||||
-3666040, -3599994, -3534204, -3402622, -3336576, -3270530,
|
||||
-3139204, -3073158, -3007112, -2875530, -2809740, -2743694,
|
||||
-2612112, -2546322, -2480276, -2348694, -2282648, -2216858,
|
||||
-2085276, -2019230, -1887905, -1887397, -1821353, -1755310,
|
||||
-1689522, -1623479, -1557435, -1491392, -1425604, -1359561,
|
||||
-1293517, -1227729, -1161686, -1095642, -1029599, -963811,
|
||||
-963304, -897260, -831473, -765425, -699377, -633329,
|
||||
-567537, -501489, -435441, -369393, -303601, -237553,
|
||||
-171505, -105713, -39665, -39153, -38641, -38385,
|
||||
-37873, -37361, -37105, -36593, -36081, -35569,
|
||||
-35313, -34801, -34289, -33777, -33521, -33009,
|
||||
-32497, -32241, -31729, -31217, -30705, -30449,
|
||||
-29937, -29425, -28913, -28657, -28145, -27633,
|
||||
-27377, -26865, -26353, -25841, -25585, -25073,
|
||||
-24561, -24305, -23793, -23281, -22769, -22513,
|
||||
-22001, -21489, -20977, -20721, -20209, -19697,
|
||||
-19441, -18929, -18417, -17905, -17649, -17137,
|
||||
-16625, -16369, -15857, -15345, -14833, -14577,
|
||||
-14065, -13553, -13041, -12785, -12273, -11761,
|
||||
-11505, -10993, -10481, -9969, -9713, -9201,
|
||||
-8689, -8433, -7914, -7394, -6875, -6611,
|
||||
-6092, -5572, -5053, -4789, -4270, -3750,
|
||||
-3487, -2967, -2448, -1928, -1665, -1145,
|
||||
-626, -106, -99, -91, -84, -76,
|
||||
-69, -61, -54, -46, -39, -31,
|
||||
-24, -16, -9, -1,
|
||||
)
|
||||
|
||||
/** Official T2E table (646 int32): temp = slope*diff>>12 + (i<<12) - 0x249f0. */
|
||||
val T2E = intArrayOf(
|
||||
51, 70, 94, 125, 162, 208, 264, 331,
|
||||
410, 503, 612, 737, 881, 1045, 1231, 1440,
|
||||
1675, 1937, 2227, 2547, 2899, 3285, 3705, 4162,
|
||||
4658, 5192, 5768, 6386, 7047, 7754, 8506, 9305,
|
||||
10153, 11050, 11997, 12995, 14045, 15147, 16303, 17513,
|
||||
18778, 20098, 21473, 22905, 24393, 25938, 27540, 29199,
|
||||
30916, 32690, 34522, 36412, 38360, 40366, 42430, 44552,
|
||||
46731, 48968, 51262, 53613, 56022, 58487, 61008, 63586,
|
||||
66220, 68910, 71654, 74454, 77308, 80217, 83179, 86195,
|
||||
89263, 92385, 95558, 98783, 102059, 105387, 108764, 112191,
|
||||
115668, 119194, 122768, 126391, 130060, 133777, 137541, 141350,
|
||||
145205, 149105, 153050, 157039, 161072, 165147, 169266, 173426,
|
||||
177629, 181873, 186157, 190482, 194847, 199251, 203694, 208175,
|
||||
212695, 217252, 221847, 226478, 231145, 235849, 240587, 245361,
|
||||
250169, 255012, 259888, 264797, 269740, 274715, 279722, 284761,
|
||||
289831, 294933, 300064, 305227, 310419, 315640, 320891, 326170,
|
||||
331478, 336814, 342178, 347569, 352988, 358433, 363904, 369402,
|
||||
374925, 380474, 386048, 391647, 397271, 402919, 408591, 414286,
|
||||
420005, 425747, 431512, 437300, 443109, 448941, 454795, 460670,
|
||||
466566, 472484, 478422, 484381, 490360, 496359, 502378, 508416,
|
||||
514474, 520551, 526647, 532761, 538894, 545045, 551215, 557402,
|
||||
563606, 569828, 576068, 582324, 588597, 594887, 601193, 607516,
|
||||
613854, 620209, 626579, 632965, 639366, 645782, 652213, 658660,
|
||||
665120, 671596, 678086, 684589, 691108, 697639, 704185, 710744,
|
||||
717317, 723903, 730502, 737114, 743740, 750377, 757028, 763691,
|
||||
770366, 777053, 783753, 790464, 797187, 803922, 810669, 817427,
|
||||
824196, 830977, 837768, 844571, 851384, 858209, 865043, 871889,
|
||||
878745, 885611, 892487, 899374, 906270, 913177, 920093, 927019,
|
||||
933954, 940899, 947854, 954818, 961791, 968773, 975764, 982764,
|
||||
989773, 996791, 1003818, 1010853, 1017897, 1024949, 1032009, 1039078,
|
||||
1046155, 1053240, 1060333, 1067435, 1074544, 1081661, 1088785, 1095918,
|
||||
1103058, 1110205, 1117360, 1124523, 1131692, 1138869, 1146053, 1153245,
|
||||
1160443, 1167648, 1174860, 1182080, 1189305, 1196538, 1203777, 1211023,
|
||||
1218276, 1225535, 1232800, 1240072, 1247350, 1254635, 1261925, 1269222,
|
||||
1276525, 1283834, 1291149, 1298470, 1305797, 1313129, 1320468, 1327812,
|
||||
1335162, 1342517, 1349878, 1357245, 1364617, 1371995, 1379378, 1386766,
|
||||
1394160, 1401559, 1408963, 1416372, 1423786, 1431206, 1438631, 1446060,
|
||||
1453495, 1460934, 1468379, 1475828, 1483282, 1490741, 1498204, 1505672,
|
||||
1513145, 1520622, 1528104, 1535591, 1543082, 1550578, 1558077, 1565582,
|
||||
1573090, 1580603, 1588121, 1595642, 1603168, 1610698, 1618232, 1625770,
|
||||
1633312, 1640859, 1648409, 1655963, 1663522, 1671084, 1678650, 1686220,
|
||||
1693794, 1701371, 1708953, 1716538, 1724127, 1731719, 1739315, 1746915,
|
||||
1754519, 1762126, 1769736, 1777350, 1784968, 1792589, 1800214, 1807842,
|
||||
1815473, 1823108, 1830746, 1838387, 1846032, 1853680, 1861331, 1868986,
|
||||
1876643, 1884304, 1891968, 1899635, 1907306, 1914979, 1922655, 1930335,
|
||||
1938017, 1945703, 1953391, 1961082, 1968777, 1976474, 1984174, 1991877,
|
||||
1999583, 2007292, 2015003, 2022718, 2030435, 2038155, 2045877, 2053602,
|
||||
2061330, 2069061, 2076794, 2084530, 2092269, 2100010, 2107754, 2115500,
|
||||
2123249, 2131001, 2138755, 2146511, 2154270, 2162031, 2169795, 2177562,
|
||||
2185330, 2193101, 2200875, 2208651, 2216429, 2224210, 2231993, 2239778,
|
||||
2247565, 2255355, 2263147, 2270941, 2278738, 2286537, 2294338, 2302141,
|
||||
2309946, 2317754, 2325563, 2333375, 2341189, 2349005, 2356823, 2364643,
|
||||
2372465, 2380290, 2388116, 2395944, 2403775, 2411607, 2419441, 2427278,
|
||||
2435116, 2442956, 2450798, 2458642, 2466488, 2474336, 2482186, 2490038,
|
||||
2497891, 2505747, 2513604, 2521463, 2529324, 2537187, 2545051, 2552918,
|
||||
2560786, 2568656, 2576527, 2584401, 2592276, 2600153, 2608031, 2615911,
|
||||
2623793, 2631677, 2639562, 2647449, 2655338, 2663228, 2671120, 2679014,
|
||||
2686909, 2694806, 2702704, 2710604, 2718505, 2726408, 2734313, 2742219,
|
||||
2750127, 2758036, 2765947, 2773859, 2781773, 2789688, 2797605, 2805524,
|
||||
2813443, 2821364, 2829287, 2837211, 2845137, 2853064, 2860992, 2868922,
|
||||
2876853, 2884786, 2892720, 2900655, 2908592, 2916530, 2924470, 2932411,
|
||||
2940353, 2948296, 2956241, 2964187, 2972135, 2980084, 2988034, 2995985,
|
||||
3003938, 3011892, 3019847, 3027803, 3035761, 3043720, 3051680, 3059642,
|
||||
3067605, 3075569, 3083534, 3091500, 3099468, 3107436, 3115406, 3123378,
|
||||
3131350, 3139323, 3147298, 3155274, 3163251, 3171229, 3179208, 3187189,
|
||||
3195170, 3203153, 3211137, 3219121, 3227107, 3235095, 3243083, 3251072,
|
||||
3259062, 3267054, 3275046, 3283040, 3291035, 3299030, 3307027, 3315025,
|
||||
3323024, 3331023, 3339024, 3347026, 3355029, 3363033, 3371038, 3379044,
|
||||
3387051, 3395059, 3403068, 3411078, 3419089, 3427101, 3435113, 3443127,
|
||||
3451142, 3459158, 3467174, 3475192, 3483211, 3491230, 3499250, 3507272,
|
||||
3515294, 3523317, 3531341, 3539366, 3547392, 3555419, 3563447, 3571475,
|
||||
3579505, 3587535, 3595566, 3603598, 3611631, 3619665, 3627700, 3635735,
|
||||
3643772, 3651809, 3659847, 3667886, 3675926, 3683966, 3692008, 3700050,
|
||||
3708093, 3716137, 3724182, 3732227, 3740273, 3748321, 3756368, 3764417,
|
||||
3772467, 3780517, 3788568, 3796620, 3804672, 3812726, 3820780, 3828835,
|
||||
3836891, 3844947, 3853004, 3861062, 3869121, 3877180, 3885240, 3893301,
|
||||
3901363, 3909425, 3917488, 3925552, 3933617, 3941682, 3949748, 3957814,
|
||||
3965882, 3973950, 3982019, 3990088, 3998158, 4006229, 4014301, 4022373,
|
||||
4030446, 4038519, 4046594, 4054669, 4062744, 4070820, 4078897, 4086975,
|
||||
4095053, 4103132, 4111212, 4119292, 4127373, 4135454, 4143536, 4151619,
|
||||
4159703, 4167787, 4175871, 4183957, 4192042, 4200129
|
||||
)
|
||||
|
||||
/** Vendor T2E curve from libcoresdk.so ARM64 @0x402010 (274 uint32 entries). */
|
||||
val T2E274 = intArrayOf(
|
||||
0x000003e8, 0x000004d3, 0x000005e1, 0x00000714, 0x0000086e, 0x000009f1, 0x00000b9e, 0x00000d76,
|
||||
0x00000f7b, 0x000011ae, 0x0000140e, 0x0000169d, 0x0000195c, 0x00001c49, 0x00001f67, 0x000022b3,
|
||||
0x0000262f, 0x000029db, 0x00002db5, 0x000031bd, 0x000035f4, 0x00003a58, 0x00003ee9, 0x000043a7,
|
||||
0x00004890, 0x00004da4, 0x000052e2, 0x0000584a, 0x00005ddb, 0x00006394, 0x00006974, 0x00006f7a,
|
||||
0x000075a6, 0x00007bf7, 0x0000826c, 0x00008904, 0x00008fbf, 0x0000969b, 0x00009d98, 0x0000a4b6,
|
||||
0x0000abf3, 0x0000b34e, 0x0000bac8, 0x0000c25f, 0x0000ca12, 0x0000d1e2, 0x0000d9cc, 0x0000e1d1,
|
||||
0x0000e9f0, 0x0000f229, 0x0000fa7a, 0x000102e3, 0x00010b64, 0x000113fb, 0x00011ca9, 0x0001256d,
|
||||
0x00012e46, 0x00013734, 0x00014036, 0x0001494c, 0x00015276, 0x00015bb2, 0x00016501, 0x00016e62,
|
||||
0x000177d4, 0x00018158, 0x00018aec, 0x00019490, 0x00019e45, 0x0001a809, 0x0001b1dd, 0x0001bbbf,
|
||||
0x0001c5b0, 0x0001cfaf, 0x0001d9bc, 0x0001e3d7, 0x0001edff, 0x0001f833, 0x00020275, 0x00020cc3,
|
||||
0x0002171d, 0x00022183, 0x00022bf5, 0x00023672, 0x000240fa, 0x00024b8c, 0x0002562a, 0x000260d2,
|
||||
0x00026b84, 0x00027640, 0x00028106, 0x00028bd5, 0x000296ae, 0x0002a190, 0x0002ac7b, 0x0002b76f,
|
||||
0x0002c26b, 0x0002cd70, 0x0002d87d, 0x0002e392, 0x0002eeaf, 0x0002f9d4, 0x00030500, 0x00031034,
|
||||
0x00031b6f, 0x000326b1, 0x000331fb, 0x00033d4b, 0x000348a2, 0x00035400, 0x00035f64, 0x00036acf,
|
||||
0x00037640, 0x000381b7, 0x00038d34, 0x000398b7, 0x0003a440, 0x0003afcf, 0x0003bb63, 0x0003c6fd,
|
||||
0x0003d29c, 0x0003de40, 0x0003e9ea, 0x0003f599, 0x0004014d, 0x00040d05, 0x000418c3, 0x00042486,
|
||||
0x0004304d, 0x00043c18, 0x000447e9, 0x000453bd, 0x00045f96, 0x00046b74, 0x00047756, 0x0004833b,
|
||||
0x00048f25, 0x00049b13, 0x0004a705, 0x0004b2fb, 0x0004bef4, 0x0004caf2, 0x0004d6f3, 0x0004e2f8,
|
||||
0x0004ef00, 0x0004fb0c, 0x0005071b, 0x0005132e, 0x00051f44, 0x00052b5d, 0x0005377a, 0x0005439a,
|
||||
0x00054fbd, 0x00055be3, 0x0005680c, 0x00057439, 0x00058068, 0x00058c9a, 0x000598cf, 0x0005a507,
|
||||
0x0005b142, 0x0005bd7f, 0x0005c9bf, 0x0005d602, 0x0005e247, 0x0005ee90, 0x0005fada, 0x00060727,
|
||||
0x00061377, 0x00061fc9, 0x00062c1e, 0x00063875, 0x000644ce, 0x00065129, 0x00065d87, 0x000669e7,
|
||||
0x0006764a, 0x000682ae, 0x00068f15, 0x00069b7e, 0x0006a7e9, 0x0006b456, 0x0006c0c5, 0x0006cd36,
|
||||
0x0006d9a9, 0x0006e61e, 0x0006f295, 0x0006ff0e, 0x00070b89, 0x00071805, 0x00072484, 0x00073104,
|
||||
0x00073d86, 0x00074a0a, 0x0007568f, 0x00076317, 0x00076fa0, 0x00077c2a, 0x000788b7, 0x00079545,
|
||||
0x0007a1d4, 0x0007ae66, 0x0007baf8, 0x0007c78d, 0x0007d423, 0x0007e0ba, 0x0007ed53, 0x0007f9ed,
|
||||
0x00080689, 0x00081327, 0x00081fc5, 0x00082c65, 0x00083907, 0x000845aa, 0x0008524e, 0x00085ef4,
|
||||
0x00086b9b, 0x00087843, 0x000884ed, 0x00089197, 0x00089e44, 0x0008aaf1, 0x0008b7a0, 0x0008c44f,
|
||||
0x0008d100, 0x0008ddb3, 0x0008ea66, 0x0008f71b, 0x000903d0, 0x00091087, 0x00091d3f, 0x000929f9,
|
||||
0x000936b3, 0x0009436e, 0x0009502b, 0x00095ce8, 0x000969a7, 0x00097666, 0x00098327, 0x00098fe9,
|
||||
0x00099cab, 0x0009a96f, 0x0009b634, 0x0009c2f9, 0x0009cfc0, 0x0009dc88, 0x0009e950, 0x0009f61a,
|
||||
0x000a02e4, 0x000a0faf, 0x000a1c7c, 0x000a2949, 0x000a3617, 0x000a42e6, 0x000a4fb5, 0x000a5c86,
|
||||
0x000a6958, 0x000a762a, 0x000a82fd, 0x000a8fd1, 0x000a9ca6, 0x000aa97c, 0x000ab652, 0x000ac329,
|
||||
0x000ad001, 0x000adcda, 0x000ae9b4, 0x000af68e, 0x000b0369, 0x000b1045, 0x000b1d22, 0x000b29ff,
|
||||
0x000b36dd, 0x000b43bc
|
||||
)
|
||||
|
||||
/** Vendor E2TAccQ10 curve from libcoresdk.so ARM64 @0x40245c (274 uint32 entries). */
|
||||
val E2T_ACC_Q10 = intArrayOf(
|
||||
0x00008b70, 0x0000795d, 0x00006abc, 0x00005eb5, 0x000054ac, 0x00004c62, 0x0000456c, 0x00003f62,
|
||||
0x00003a34, 0x000035e5, 0x00003207, 0x00002e9d, 0x00002bc0, 0x00002910, 0x000026d3, 0x000024bc,
|
||||
0x000022dc, 0x0000213c, 0x00001fc0, 0x00001e5e, 0x00001d27, 0x00001c08, 0x00001afe, 0x00001a12,
|
||||
0x00001935, 0x0000186b, 0x000017ad, 0x000016ff, 0x0000165e, 0x000015ca, 0x00001540, 0x000014bd,
|
||||
0x00001444, 0x000013d3, 0x0000136a, 0x00001305, 0x000012a9, 0x00001251, 0x000011fc, 0x000011af,
|
||||
0x00001167, 0x0000111f, 0x000010dd, 0x000010a0, 0x00001062, 0x0000102c, 0x00000ff6, 0x00000fc3,
|
||||
0x00000f91, 0x00000f64, 0x00000f38, 0x00000f0d, 0x00000ee7, 0x00000ebf, 0x00000e9a, 0x00000e78,
|
||||
0x00000e56, 0x00000e36, 0x00000e16, 0x00000df8, 0x00000ddc, 0x00000dc0, 0x00000da6, 0x00000d8d,
|
||||
0x00000d74, 0x00000d5d, 0x00000d47, 0x00000d30, 0x00000d1b, 0x00000d06, 0x00000cf4, 0x00000ce0,
|
||||
0x00000cce, 0x00000cbc, 0x00000cab, 0x00000c9a, 0x00000c8c, 0x00000c7a, 0x00000c6c, 0x00000c5e,
|
||||
0x00000c4f, 0x00000c41, 0x00000c34, 0x00000c28, 0x00000c1c, 0x00000c0e, 0x00000c03, 0x00000bf8,
|
||||
0x00000bed, 0x00000be2, 0x00000bd8, 0x00000bcd, 0x00000bc3, 0x00000bb9, 0x00000bb0, 0x00000ba7,
|
||||
0x00000b9e, 0x00000b95, 0x00000b8d, 0x00000b85, 0x00000b7c, 0x00000b75, 0x00000b6d, 0x00000b66,
|
||||
0x00000b5f, 0x00000b57, 0x00000b51, 0x00000b4a, 0x00000b43, 0x00000b3d, 0x00000b36, 0x00000b30,
|
||||
0x00000b2a, 0x00000b24, 0x00000b1e, 0x00000b19, 0x00000b13, 0x00000b0e, 0x00000b08, 0x00000b04,
|
||||
0x00000aff, 0x00000af9, 0x00000af5, 0x00000af0, 0x00000aec, 0x00000ae7, 0x00000ae2, 0x00000ade,
|
||||
0x00000adb, 0x00000ad5, 0x00000ad2, 0x00000ace, 0x00000ac9, 0x00000ac6, 0x00000ac3, 0x00000abe,
|
||||
0x00000abb, 0x00000ab7, 0x00000ab4, 0x00000ab1, 0x00000aac, 0x00000aaa, 0x00000aa6, 0x00000aa4,
|
||||
0x00000aa0, 0x00000a9d, 0x00000a9a, 0x00000a97, 0x00000a95, 0x00000a91, 0x00000a8f, 0x00000a8c,
|
||||
0x00000a89, 0x00000a87, 0x00000a83, 0x00000a82, 0x00000a7f, 0x00000a7c, 0x00000a7a, 0x00000a77,
|
||||
0x00000a76, 0x00000a73, 0x00000a70, 0x00000a6f, 0x00000a6b, 0x00000a6a, 0x00000a68, 0x00000a65,
|
||||
0x00000a64, 0x00000a61, 0x00000a5f, 0x00000a5e, 0x00000a5c, 0x00000a5a, 0x00000a58, 0x00000a55,
|
||||
0x00000a55, 0x00000a52, 0x00000a50, 0x00000a4f, 0x00000a4d, 0x00000a4b, 0x00000a4a, 0x00000a48,
|
||||
0x00000a46, 0x00000a45, 0x00000a43, 0x00000a42, 0x00000a41, 0x00000a3e, 0x00000a3d, 0x00000a3c,
|
||||
0x00000a3a, 0x00000a39, 0x00000a37, 0x00000a36, 0x00000a35, 0x00000a33, 0x00000a32, 0x00000a31,
|
||||
0x00000a2f, 0x00000a2f, 0x00000a2c, 0x00000a2c, 0x00000a2b, 0x00000a29, 0x00000a28, 0x00000a27,
|
||||
0x00000a25, 0x00000a25, 0x00000a23, 0x00000a22, 0x00000a21, 0x00000a20, 0x00000a1f, 0x00000a1e,
|
||||
0x00000a1d, 0x00000a1b, 0x00000a1b, 0x00000a19, 0x00000a19, 0x00000a17, 0x00000a17, 0x00000a16,
|
||||
0x00000a14, 0x00000a14, 0x00000a13, 0x00000a13, 0x00000a11, 0x00000a10, 0x00000a0f, 0x00000a0f,
|
||||
0x00000a0e, 0x00000a0c, 0x00000a0c, 0x00000a0b, 0x00000a0b, 0x00000a09, 0x00000a08, 0x00000a08,
|
||||
0x00000a07, 0x00000a06, 0x00000a06, 0x00000a05, 0x00000a04, 0x00000a04, 0x00000a02, 0x00000a02,
|
||||
0x00000a01, 0x00000a00, 0x00000a00, 0x000009ff, 0x000009fe, 0x000009fe, 0x000009fd, 0x000009fc,
|
||||
0x000009fc, 0x000009fb, 0x000009fa, 0x000009fa, 0x000009f9, 0x000009f9, 0x000009f8, 0x000009f7,
|
||||
0x000009f7, 0x000009f6, 0x000009f6, 0x000009f5, 0x000009f4, 0x000009f3, 0x000009f3, 0x000009f3,
|
||||
0x000009f2, 0x000009f1
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,686 @@
|
||||
package com.mag160c.thermal.core
|
||||
|
||||
import kotlin.math.sqrt
|
||||
|
||||
/**
|
||||
* Official MAG160C rendering pipeline, ported from csdk/src/mag160c_render.c
|
||||
* (pixel-verified against CoreSDKLib.dll).
|
||||
*
|
||||
* raw frame -> shutter extract -> FFC state machine -> endpoint select
|
||||
* + Q12 table interp -> ref (4x type-1 mean) -> NUC lookup -> blind
|
||||
* compensation -> stats/window -> LUT1024 rebuild -> gray -> 2x upscale
|
||||
* -> palette -> ARGB output.
|
||||
*
|
||||
* Feed USB frames; get a 320x240 ARGB IntArray ready for Bitmap.setPixels.
|
||||
* FFC commands are emitted through [onFfc] so the caller owns the transport.
|
||||
*/
|
||||
class RenderPipeline(
|
||||
private val w: Int = 160,
|
||||
private val h: Int = 120,
|
||||
private val ffcPeriod: Int = 1800,
|
||||
private val ffcDrift: Int = 250,
|
||||
private val warmFrames: Int = 20,
|
||||
private val cdfPivot75: Boolean = true,
|
||||
private val force75: Boolean = true,
|
||||
/** FFC command callback: param 0 = FFC(0), 1 = FFC(1). */
|
||||
private val onFfc: ((param: Int) -> Unit)? = null,
|
||||
) {
|
||||
private val npix = w * h
|
||||
|
||||
// ---- DDT state ----
|
||||
private var epCount = 0
|
||||
private var nsegs = 0
|
||||
private var c5c = 0
|
||||
private var c60 = 0
|
||||
private var dev24 = 5
|
||||
private var dev4cShift = 3
|
||||
private var dev48 = 0
|
||||
private val epTemps = IntArray(64)
|
||||
private val blindCnt = IntArray(64)
|
||||
private val epThr = arrayOfNulls<ShortArray>(64)
|
||||
private val epGain = arrayOfNulls<IntArray>(64)
|
||||
private val epBlind = arrayOfNulls<IntArray>(64)
|
||||
private var sel = 0
|
||||
private var thrWork = ShortArray(0)
|
||||
private var gainWork = IntArray(0)
|
||||
private var blindWork: IntArray? = null
|
||||
private var blindCount = 0
|
||||
|
||||
// ---- frame/FFC state ----
|
||||
private var ffcIdx = -1
|
||||
private var globalFrames = 0
|
||||
private var shutter = 0
|
||||
private var shutterAtFfc = 0
|
||||
private var manualFfc = false
|
||||
private var hasRef = false
|
||||
private var refCnt = 0
|
||||
private var startupFfcDone = false
|
||||
|
||||
private val ref = IntArray(npix)
|
||||
private val refAcc = IntArray(npix)
|
||||
private val nuc = IntArray(npix)
|
||||
private val gray160 = ByteArray(npix)
|
||||
private val gray320 = ByteArray(npix * 4)
|
||||
|
||||
// ---- render state ----
|
||||
private val hist1024 = IntArray(1024)
|
||||
private val cdf = IntArray(1024)
|
||||
private val lut = ByteArray(1024)
|
||||
private val curve = IntArray(1024)
|
||||
private var winLo = 0
|
||||
private var winHi = 0
|
||||
private var statMin = 0
|
||||
private var statMax = 0
|
||||
private var statMean = 0
|
||||
private var statStd = 0
|
||||
private var centerPrev = 341
|
||||
private var meanPrev = 0
|
||||
|
||||
// ---- output ----
|
||||
val outWidth: Int = w * 2
|
||||
val outHeight: Int = h * 2
|
||||
|
||||
private val lock = Any()
|
||||
|
||||
/** Load the official DDT calibration data (raw file bytes). */
|
||||
fun loadDdt(bytes: ByteArray): Boolean = synchronized(lock) {
|
||||
if (bytes.size < 0x98) return false
|
||||
val magic = ByteReader.rd32(bytes, 0)
|
||||
if (magic != 0x5AA50003 && magic != 0x5AA50004) return false
|
||||
if (ByteReader.rd32(bytes, 4) != w || ByteReader.rd32(bytes, 8) != h) return false
|
||||
epCount = ByteReader.rd32(bytes, 12)
|
||||
nsegs = ByteReader.rd32(bytes, 16)
|
||||
if (epCount < 2 || epCount > 64 || nsegs < 1 || nsegs > 8) return false
|
||||
val v4 = magic == 0x5AA50004
|
||||
dev24 = ByteReader.rd32(bytes, 20)
|
||||
dev4cShift = 3
|
||||
if (!v4) {
|
||||
c5c = ByteReader.rd32(bytes, 28)
|
||||
c60 = ByteReader.rd32(bytes, 32)
|
||||
if (c5c > 0x10000) c5c = 0
|
||||
} else {
|
||||
c5c = ByteReader.rd32(bytes, 0x2C + 0x120)
|
||||
c60 = ByteReader.rd32(bytes, 0x30 + 0x120)
|
||||
}
|
||||
if (c5c and 1 == 1) c5c = c5c and 1.inv()
|
||||
val arro = if (v4) 0x120 + 36 else 36
|
||||
var off = arro
|
||||
if (off + epCount * 12 > bytes.size) return false
|
||||
for (e in 0 until epCount) {
|
||||
epTemps[e] = ByteReader.rd32(bytes, off + e * 4)
|
||||
}
|
||||
off += epCount * 4
|
||||
off += epCount * 4 // skip array 2
|
||||
off += epCount * 4 // skip array 3
|
||||
if (off + (epCount - 1) * 4 > bytes.size) return false
|
||||
for (e in 0 until epCount - 1) {
|
||||
blindCnt[e] = ByteReader.rd32(bytes, off + e * 4)
|
||||
if (blindCnt[e] > 4096) blindCnt[e] = 4096
|
||||
}
|
||||
blindCnt[epCount - 1] = 0
|
||||
off += (epCount - 1) * 4
|
||||
|
||||
val thrSz = (nsegs - 1) * npix + c5c / 2
|
||||
val gainSz = nsegs * npix * 2
|
||||
for (e in 0 until epCount) {
|
||||
if (off + thrSz * 2 + gainSz * 2 > bytes.size) return false
|
||||
val thr = ShortArray(thrSz)
|
||||
for (i in 0 until thrSz) {
|
||||
thr[i] = ByteReader.rd16(bytes, off + i * 2).toShort()
|
||||
}
|
||||
off += thrSz * 2
|
||||
val gain = IntArray(gainSz)
|
||||
ByteReader.decodeU16(bytes, off, gainSz, gain)
|
||||
off += gainSz * 2
|
||||
epThr[e] = thr
|
||||
epGain[e] = gain
|
||||
}
|
||||
for (e in 0 until epCount - 1) {
|
||||
val n = blindCnt[e]
|
||||
if (n > 0) {
|
||||
if (off + n * 40 > bytes.size) return false
|
||||
val rec = IntArray(n * 10)
|
||||
for (i in 0 until n * 10) {
|
||||
rec[i] = ByteReader.rd32(bytes, off + i * 4)
|
||||
}
|
||||
off += n * 40
|
||||
epBlind[e] = rec
|
||||
} else {
|
||||
epBlind[e] = null
|
||||
}
|
||||
}
|
||||
thrWork = ShortArray(thrSz)
|
||||
gainWork = IntArray(gainSz)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun selectEndpoint(shutter: Int): Int {
|
||||
var s = 0
|
||||
if (epCount != 2) {
|
||||
while (s < epCount - 2 && shutter > epTemps[s + 1]) s++
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
private fun rebuildTables(shutter: Int) {
|
||||
sel = selectEndpoint(shutter)
|
||||
val t = (((shutter - epTemps[sel]) shl 12) / (epTemps[sel + 1] - epTemps[sel]))
|
||||
.coerceIn(-0x3FFF, 0x3FFF)
|
||||
val a = epThr[sel]!!
|
||||
val b = epThr[sel + 1]!!
|
||||
val nThr = (nsegs - 1) * npix + c5c / 2
|
||||
for (i in 0 until nThr) {
|
||||
val av = a[i].toInt()
|
||||
thrWork[i] = (av + (((b[i].toInt() - av) * t) shr 12)).toShort()
|
||||
}
|
||||
val ga = epGain[sel]!!
|
||||
val gb = epGain[sel + 1]!!
|
||||
val nGain = nsegs * npix * 2
|
||||
for (i in 0 until nGain) {
|
||||
gainWork[i] = ga[i] + (((gb[i] - ga[i]) * t) shr 12)
|
||||
}
|
||||
blindWork = epBlind[sel]
|
||||
blindCount = blindCnt[sel]
|
||||
}
|
||||
|
||||
private fun refPush(frame: IntArray) {
|
||||
if (refCnt == 0) {
|
||||
for (i in 0 until npix) refAcc[i] = frame[i]
|
||||
} else {
|
||||
for (i in 0 until npix) refAcc[i] += frame[i]
|
||||
}
|
||||
refCnt++
|
||||
if (refCnt >= 4) {
|
||||
for (i in 0 until npix) ref[i] = refAcc[i] shr 2
|
||||
refCnt = 0
|
||||
hasRef = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun nucAndBlind(f20: IntArray, out: IntArray) {
|
||||
val thr = thrWork
|
||||
val gg = gainWork
|
||||
for (i in 0 until npix) {
|
||||
val d2 = (f20[i] - ref[i]) shr 1
|
||||
var seg = 0
|
||||
if (d2 > thr[i * 2].toInt()) {
|
||||
seg = 1
|
||||
if (d2 > thr[i * 2 + 1].toInt()) seg = 2
|
||||
}
|
||||
val base = (seg * npix + i) * 2
|
||||
var v = gg[base + 1] + ((gg[base] * d2) shr 12)
|
||||
if (v < 0) v = 0 else if (v > 65535) v = 65535
|
||||
out[i] = v
|
||||
}
|
||||
val bw = blindWork
|
||||
if (bw != null && blindCount > 0) {
|
||||
for (k in 0 until blindCount) {
|
||||
val base = k * 10
|
||||
val target = bw[base]
|
||||
val typ = bw[base + 1]
|
||||
if (target >= npix || typ < 3 || typ > 8) continue
|
||||
var sum = 0L
|
||||
for (j in 0 until typ) sum += out[bw[base + 2 + j]]
|
||||
when (typ) {
|
||||
8 -> out[target] = (sum shr 3).toInt()
|
||||
4 -> out[target] = (sum shr 2).toInt()
|
||||
else -> out[target] = (sum / typ).toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun statsWindow() {
|
||||
var ssum = 0L
|
||||
var s2sum = 0L
|
||||
var mn = 0xFFFF
|
||||
var mx = 0
|
||||
for (i in 0 until npix) {
|
||||
val v = nuc[i]
|
||||
ssum += v
|
||||
s2sum += v.toLong() * v
|
||||
if (v < mn) mn = v
|
||||
if (v > mx) mx = v
|
||||
}
|
||||
statMin = mn
|
||||
statMax = mx
|
||||
statMean = (ssum / npix).toInt()
|
||||
val dmean = ssum.toDouble() / npix
|
||||
val variance = s2sum.toDouble() / npix - dmean * dmean
|
||||
statStd = sqrt(if (variance > 0) variance else 0.0).toInt()
|
||||
if (mn == mx) {
|
||||
winLo = mn
|
||||
winHi = mn
|
||||
lut.fill(0x80.toByte())
|
||||
gray160.fill(0x80.toByte())
|
||||
return
|
||||
}
|
||||
var base = (dev24 * 1000) shr dev4cShift
|
||||
if (base < 0x80) base = 0x80
|
||||
val half = base shr 1
|
||||
var lo = statMean - half
|
||||
if (lo > statMin) lo = statMin
|
||||
if (lo < 0) lo = 0
|
||||
var hi = statMean + half
|
||||
if (hi < statMax) hi = statMax
|
||||
if (hi > 65535) hi = 65535
|
||||
winLo = lo
|
||||
winHi = hi
|
||||
if (winHi <= winLo) winHi = winLo + 1
|
||||
}
|
||||
|
||||
private fun lutRebuild() {
|
||||
// The vendor code is 32-bit unsigned arithmetic; products/subtractions
|
||||
// wrap mod 2^32. u32() replicates that so results match bit-for-bit.
|
||||
val span = (winHi - winLo).coerceAtLeast(1)
|
||||
val s = 0xFFC00000L / span
|
||||
hist1024.fill(0)
|
||||
for (i in 0 until npix) {
|
||||
val idx = u32((nuc[i] - winLo).toLong() * s) shr 0x16
|
||||
if (idx < 1024) hist1024[idx.toInt()]++
|
||||
}
|
||||
var i = 0
|
||||
while (i < 1023) {
|
||||
hist1024[i] = (hist1024[i + 1] + hist1024[i]) shr 1
|
||||
hist1024[i + 1] = (hist1024[i + 2] + hist1024[i + 1]) shr 1
|
||||
hist1024[i + 2] = (hist1024[i + 3] + hist1024[i + 2]) shr 1
|
||||
i += 3
|
||||
}
|
||||
var total = 0L
|
||||
for (k in 0 until 1024) total += hist1024[k]
|
||||
var u21 = total shr 12
|
||||
if (u21 == 0L) u21 = 1
|
||||
var lres = ((statMean - winLo).toLong() * 0x3FF / span).toInt()
|
||||
if (lres < 0) lres = 0
|
||||
if (lres > 1023) lres = 1023
|
||||
if (cdfPivot75) {
|
||||
var acc = 0L
|
||||
var k = 0
|
||||
while (k < 1024) {
|
||||
acc += hist1024[k]
|
||||
if (acc >= (total * 3 shr 2)) break
|
||||
k++
|
||||
}
|
||||
if (k < 1024 && k > lres) lres = k
|
||||
}
|
||||
var u11 = 0L
|
||||
var u13 = 0L
|
||||
var k = lres
|
||||
while (k >= 0) {
|
||||
u11 += hist1024[k]
|
||||
if (u21 <= u11) {
|
||||
u13 += 0x100
|
||||
u11 = 0
|
||||
}
|
||||
cdf[k] = (((0x10000L / u21) * u11 shr 8) + u13).toInt()
|
||||
k--
|
||||
}
|
||||
u11 = 0
|
||||
u13 = 0
|
||||
k = lres + 1
|
||||
while (k < 1024) {
|
||||
u11 += hist1024[k]
|
||||
if (u11 < u21 * 8) {
|
||||
if (u21 <= u11) {
|
||||
u13 += 0x100
|
||||
u11 = 0
|
||||
}
|
||||
} else {
|
||||
u13 += 0x200
|
||||
u11 = 0
|
||||
}
|
||||
cdf[k] = (((0x10000L / u21) * u11 shr 8) + u13).toInt()
|
||||
k++
|
||||
}
|
||||
for (j in 0 until 1024) {
|
||||
val v = ((0x300000L + j * 0x800L) shr 0xD).toInt()
|
||||
curve[j] = if (v != 0) v else 1
|
||||
}
|
||||
var iv6 = dev48 * 0x155 / 200
|
||||
var iv7 = iv6 + 0x155
|
||||
var iv26 = iv7
|
||||
if (iv6 + 0x156 > 0x331) iv26 = 0x330
|
||||
var iv10 = 0x332
|
||||
var b3 = true
|
||||
val iv22 = 0xFFC00000L / span
|
||||
while (iv26 + 1 < iv10) {
|
||||
iv7 = (iv10 + iv26) / 2
|
||||
if (b3) {
|
||||
b3 = false
|
||||
iv7 = iv26
|
||||
}
|
||||
var u5 = cdf[0].toLong()
|
||||
if (u5 != 0L) {
|
||||
val denom = (iv7 * 0x40000L + 0x40000L) / u5
|
||||
val base = iv7 * 0x10L + 0x10L
|
||||
var u21v = base
|
||||
var j = lres
|
||||
while (j >= 0) {
|
||||
val shifted = u32(cdf[j].toLong() * denom) shr 0xE
|
||||
var u12 = u32(base - shifted)
|
||||
val over = u32(u21v - u12)
|
||||
if (curve[j] < over) u12 = u32(u21v - curve[j])
|
||||
lut[j] = (u12 shr 6).toByte()
|
||||
u21v = u12
|
||||
j--
|
||||
}
|
||||
}
|
||||
u5 = cdf[1023].toLong()
|
||||
if (u5 != 0L) {
|
||||
val denom = u32(0xFFC0000L - iv7 * 0x40000L) / u5
|
||||
val base = iv7 * 0x10L
|
||||
var u21v = base
|
||||
var j = lres + 1
|
||||
while (j < 1024) {
|
||||
val shifted = u32(cdf[j].toLong() * denom) shr 0xE
|
||||
var u12 = u32(shifted + base)
|
||||
val over = u32(u12 - u21v)
|
||||
if (curve[j] < over) u12 = u32(curve[j] + u21v)
|
||||
lut[j] = (u12 shr 6).toByte()
|
||||
u21v = u12
|
||||
j++
|
||||
}
|
||||
}
|
||||
var iv4 = iv7
|
||||
val bfmin = u32(u32((statMin - winLo).toLong()) * iv22) shr 0x16
|
||||
val bfmax = u32(u32((statMax - winLo).toLong()) * iv22) shr 0x16
|
||||
if (bfmin < 1024 && bfmax < 1024 &&
|
||||
(lut[bfmin.toInt()].toInt() and 0xFF) == 0 && (lut[bfmax.toInt()].toInt() and 0xFF) != 0xFF
|
||||
) {
|
||||
iv4 = iv10
|
||||
iv26 = iv7
|
||||
}
|
||||
iv10 = iv4
|
||||
}
|
||||
var dm = statMean - meanPrev
|
||||
if (dm < 0) dm = -dm
|
||||
meanPrev = statMean
|
||||
var iv18 = iv10 + ((dm and 0xFFFF) shl dev4cShift) * 2 + statMean
|
||||
if (iv18 < 0x9C4) {
|
||||
if (iv18 < 100) iv18 = 100
|
||||
iv7 = (iv18 * iv7 + centerPrev * 500) / (iv18 + 500)
|
||||
centerPrev = iv7
|
||||
var u5 = cdf[0].toLong()
|
||||
if (u5 != 0L) {
|
||||
val denom = (iv7 * 0x40000L + 0x40000L) / u5
|
||||
val base = iv7 * 0x10L + 0x10L
|
||||
var u21v = base
|
||||
var j = lres
|
||||
while (j >= 0) {
|
||||
val shifted = u32(cdf[j].toLong() * denom) shr 0xE
|
||||
var u12 = u32(base - shifted)
|
||||
val over = u32(u21v - u12)
|
||||
if (curve[j] < over) u12 = u32(u21v - curve[j])
|
||||
lut[j] = (u12 shr 6).toByte()
|
||||
u21v = u12
|
||||
j--
|
||||
}
|
||||
}
|
||||
u5 = cdf[1023].toLong()
|
||||
if (u5 != 0L) {
|
||||
val denom = u32(0xFFC0000L - iv7 * 0x40000L) / u5
|
||||
val base = iv7 * 0x10L
|
||||
var u21v = base
|
||||
var j = lres + 1
|
||||
while (j < 1024) {
|
||||
val shifted = u32(cdf[j].toLong() * denom) shr 0xE
|
||||
var u12 = u32(shifted + base)
|
||||
val over = u32(u12 - u21v)
|
||||
if (curve[j] < over) u12 = u32(curve[j] + u21v)
|
||||
lut[j] = (u12 shr 6).toByte()
|
||||
u21v = u12
|
||||
j++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun grayMap() {
|
||||
val s = 0xFFC00000L / (winHi - winLo).coerceAtLeast(1)
|
||||
for (i in 0 until npix) {
|
||||
val v = nuc[i]
|
||||
val idx = when {
|
||||
v <= winLo -> 0L
|
||||
v >= winHi -> 1023L
|
||||
else -> u32((v - winLo).toLong() * s) shr 0x16
|
||||
}
|
||||
gray160[i] = lut[idx.coerceAtMost(1023L).toInt()]
|
||||
}
|
||||
}
|
||||
|
||||
/** 32-bit unsigned wrap (C unsigned int semantics). */
|
||||
private fun u32(x: Long): Long = x and 0xFFFFFFFFL
|
||||
|
||||
private fun upscale2x() {
|
||||
val W = w
|
||||
val H = h
|
||||
val W2 = W * 2
|
||||
val s = gray160
|
||||
val o = gray320
|
||||
for (y in 0 until H - 1) {
|
||||
val r0 = y * W
|
||||
val r1 = r0 + W
|
||||
val o0 = y * W2 * 2
|
||||
val o1 = o0 + W2
|
||||
var x = 0
|
||||
while (x < W - 2) {
|
||||
val a0 = s[r0 + x].toInt() and 0xFF
|
||||
val a1 = s[r0 + x + 1].toInt() and 0xFF
|
||||
val a2 = s[r0 + x + 2].toInt() and 0xFF
|
||||
val b0 = s[r1 + x].toInt() and 0xFF
|
||||
val b1 = s[r1 + x + 1].toInt() and 0xFF
|
||||
val b2 = s[r1 + x + 2].toInt() and 0xFF
|
||||
val c = x * 2
|
||||
o[o0 + c] = a0.toByte()
|
||||
o[o0 + c + 1] = ((a0 + a1) shr 1).toByte()
|
||||
o[o0 + c + 2] = a1.toByte()
|
||||
o[o0 + c + 3] = ((a2 + a1) shr 1).toByte()
|
||||
o[o1 + c] = ((b0 + a0) shr 1).toByte()
|
||||
o[o1 + c + 1] = ((b0 + b1 + a0 + a1) shr 2).toByte()
|
||||
o[o1 + c + 2] = ((b1 + a1) shr 1).toByte()
|
||||
o[o1 + c + 3] = ((b2 + b1 + a2 + a1) shr 2).toByte()
|
||||
x += 2
|
||||
}
|
||||
val a0 = s[r0 + W - 2].toInt() and 0xFF
|
||||
val a1 = s[r0 + W - 1].toInt() and 0xFF
|
||||
val b0 = s[r1 + W - 2].toInt() and 0xFF
|
||||
val b1 = s[r1 + W - 1].toInt() and 0xFF
|
||||
var c = (W - 2) * 2
|
||||
o[o0 + c] = a0.toByte()
|
||||
o[o0 + c + 1] = ((a1 + a0) shr 1).toByte()
|
||||
o[o1 + c] = ((b0 + a0) shr 1).toByte()
|
||||
o[o1 + c + 1] = ((a1 + b1 + b0 + a0) shr 2).toByte()
|
||||
c = (W - 1) * 2
|
||||
o[o0 + c] = a1.toByte()
|
||||
o[o0 + c + 1] = (((3 * a1) shr 2) + (a0 shr 2)).toByte()
|
||||
o[o1 + c] = ((b1 + a1) shr 1).toByte()
|
||||
o[o1 + c + 1] = ((3 * (b1 + a1) + b0 + a0) shr 3).toByte()
|
||||
}
|
||||
val y = H - 1
|
||||
val r0 = y * W
|
||||
val r1 = r0 - W
|
||||
val o0 = y * W2 * 2
|
||||
val o1 = o0 + W2
|
||||
var x = 0
|
||||
while (x < W - 2) {
|
||||
val a0 = s[r0 + x].toInt() and 0xFF
|
||||
val a1 = s[r0 + x + 1].toInt() and 0xFF
|
||||
val a2 = s[r0 + x + 2].toInt() and 0xFF
|
||||
val u0 = s[r1 + x].toInt() and 0xFF
|
||||
val u1 = s[r1 + x + 1].toInt() and 0xFF
|
||||
val u2 = s[r1 + x + 2].toInt() and 0xFF
|
||||
val c = x * 2
|
||||
o[o0 + c] = a0.toByte()
|
||||
o[o0 + c + 1] = ((a0 + a1) shr 1).toByte()
|
||||
o[o0 + c + 2] = a1.toByte()
|
||||
o[o0 + c + 3] = ((a2 + a1) shr 1).toByte()
|
||||
o[o1 + c] = (((3 * a0) shr 2) + (u0 shr 2)).toByte()
|
||||
o[o1 + c + 1] = ((3 * (a0 + a1) + u0 + u1) shr 3).toByte()
|
||||
o[o1 + c + 2] = (((3 * a1) shr 2) + (u1 shr 2)).toByte()
|
||||
o[o1 + c + 3] = ((3 * (a2 + a1) + u2 + u1) shr 3).toByte()
|
||||
x += 2
|
||||
}
|
||||
val a0 = s[r0 + W - 2].toInt() and 0xFF
|
||||
val a1 = s[r0 + W - 1].toInt() and 0xFF
|
||||
val u0 = s[r1 + W - 2].toInt() and 0xFF
|
||||
val u1 = s[r1 + W - 1].toInt() and 0xFF
|
||||
var c = (W - 2) * 2
|
||||
o[o0 + c] = a0.toByte()
|
||||
o[o0 + c + 1] = ((a1 + a0) shr 1).toByte()
|
||||
o[o1 + c] = (((3 * a0) shr 2) + (u0 shr 2)).toByte()
|
||||
o[o1 + c + 1] = ((3 * (a0 + a1) + u0 + u1) shr 3).toByte()
|
||||
c = (W - 1) * 2
|
||||
o[o0 + c] = a1.toByte()
|
||||
o[o0 + c + 1] = ((3 * a1 + a0) shr 2).toByte()
|
||||
o[o1 + c] = ((3 * a1 + u1) shr 2).toByte()
|
||||
o[o1 + c + 1] = (((o[o0 + c + 1].toInt() and 0xFF) + (o[o1 + c].toInt() and 0xFF) + a1) / 3).toByte()
|
||||
}
|
||||
|
||||
private fun ffcStep(shutterIn: Int): Int {
|
||||
if (ffcIdx < 0) {
|
||||
onFfc?.invoke(0)
|
||||
ffcIdx = 0
|
||||
shutterAtFfc = shutterIn
|
||||
}
|
||||
ffcIdx++
|
||||
val idx = ffcIdx
|
||||
if (idx <= 5) {
|
||||
if (idx == 1) rebuildTables(shutterAtFfc)
|
||||
return 1
|
||||
}
|
||||
if (idx <= 9) {
|
||||
if (idx == 9) onFfc?.invoke(1)
|
||||
if (idx == 6) {
|
||||
refCnt = 0
|
||||
hasRef = false
|
||||
}
|
||||
return 2 // ref collection window
|
||||
}
|
||||
if (idx <= 13) return 1
|
||||
// normal frame: FFC condition
|
||||
var drift = shutterIn - shutterAtFfc
|
||||
if (drift < 0) drift = -drift
|
||||
val cool = 13
|
||||
var doFfc = false
|
||||
if (manualFfc) {
|
||||
manualFfc = false
|
||||
doFfc = true
|
||||
}
|
||||
if (force75 && !startupFfcDone && idx == 75) {
|
||||
startupFfcDone = true
|
||||
doFfc = true
|
||||
}
|
||||
if (idx >= 4 + ffcPeriod) {
|
||||
doFfc = true
|
||||
} else if (idx >= cool + 30 && drift > ffcDrift) {
|
||||
doFfc = true
|
||||
}
|
||||
if (doFfc) {
|
||||
onFfc?.invoke(0)
|
||||
ffcIdx = 0
|
||||
shutterAtFfc = shutterIn
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Process one raw frame.
|
||||
* @param frame USB frame buffer (with or without the 0x38-byte header)
|
||||
* @param hasHdr true when the 28-byte header is at frame[0]
|
||||
* @param outArgb 320x240 ARGB IntArray output (outW*outH entries)
|
||||
* @return true when outArgb was written; false while in the startup/FFC window.
|
||||
*/
|
||||
fun frame(frame: ByteArray, hasHdr: Boolean, outArgb: IntArray): Boolean = synchronized(lock) {
|
||||
globalFrames++
|
||||
if (globalFrames < warmFrames) return false
|
||||
|
||||
var shutterValue = shutter
|
||||
if (hasHdr) {
|
||||
val len = ByteReader.rd32(frame, 8)
|
||||
if (len == npix * 2) {
|
||||
shutterValue = ByteReader.rd32(frame, 0x1C + len + 8)
|
||||
}
|
||||
}
|
||||
shutter = shutterValue
|
||||
|
||||
val st = ffcStep(shutterValue)
|
||||
if (st != 0) {
|
||||
if (st == 2) {
|
||||
val off = if (hasHdr) 0x1C else 0
|
||||
ByteReader.decodeU16(frame, off, npix, nuc)
|
||||
refPush(nuc)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
val off = if (hasHdr) 0x1C else 0
|
||||
ByteReader.decodeU16(frame, off, npix, nuc)
|
||||
if (!hasRef) return false
|
||||
|
||||
nucAndBlind(nuc, nuc)
|
||||
statsWindow()
|
||||
lutRebuild()
|
||||
grayMap()
|
||||
upscale2x()
|
||||
// palette colorize 320x240 -> ARGB
|
||||
val pal = OfficialTables.PALETTE256_ARGB
|
||||
var i = 0
|
||||
while (i < npix * 4) {
|
||||
outArgb[i] = pal[gray320[i].toInt() and 0xFF]
|
||||
i++
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun setShutter(value: Int) {
|
||||
synchronized(lock) { shutter = value }
|
||||
}
|
||||
|
||||
/** Manual FFC trigger (official FFC button). */
|
||||
fun requestFfc() {
|
||||
synchronized(lock) { manualFfc = true }
|
||||
}
|
||||
|
||||
data class Stats(
|
||||
val winLo: Int, val winHi: Int,
|
||||
val min: Int, val max: Int,
|
||||
val mean: Int, val std: Int,
|
||||
)
|
||||
|
||||
fun stats(): Stats = synchronized(lock) {
|
||||
Stats(winLo, winHi, statMin, statMax, statMean, statStd)
|
||||
}
|
||||
|
||||
fun hasReference(): Boolean = synchronized(lock) { hasRef }
|
||||
fun frameIndex(): Int = synchronized(lock) { ffcIdx }
|
||||
fun endpoint(): Int = synchronized(lock) { sel }
|
||||
|
||||
/** Temperature probe on the current NUC output, in millidegrees C. */
|
||||
fun probeTemp(x: Int, y: Int): Int = synchronized(lock) {
|
||||
TempMath.countsToTempMc(nuc[y * w + x])
|
||||
}
|
||||
|
||||
/** Read the current NUC counts buffer (already blind-compensated). */
|
||||
fun copyNuc(out: IntArray) = synchronized(lock) {
|
||||
System.arraycopy(nuc, 0, out, 0, minOf(npix, out.size))
|
||||
}
|
||||
|
||||
/** Debug: full intermediate state, same layout as analysis/debug_lut.c. */
|
||||
internal fun debugState(): String = synchronized(lock) {
|
||||
val sb = StringBuilder()
|
||||
sb.append("win $winLo $winHi\n")
|
||||
sb.append("hist")
|
||||
for (v in hist1024) sb.append(' ').append(v)
|
||||
sb.append("\ncdf")
|
||||
for (v in cdf) sb.append(' ').append(v)
|
||||
sb.append("\nlut")
|
||||
for (v in lut) sb.append(' ').append(v.toInt() and 0xFF)
|
||||
sb.append("\ngray")
|
||||
for (v in gray160) sb.append(' ').append(v.toInt() and 0xFF)
|
||||
sb.append("\ngray320")
|
||||
for (v in gray320) sb.append(' ').append(v.toInt() and 0xFF)
|
||||
sb.append('\n')
|
||||
sb.toString()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.mag160c.thermal.core
|
||||
|
||||
/**
|
||||
* Temperature math ported from csdk/src/mag160c_render.c counts_to_temp_mc
|
||||
* and csdk/src/mag160c_temp.c t2e_interp. All temperatures are integer
|
||||
* millidegrees Celsius (0.001 C units).
|
||||
*/
|
||||
object TempMath {
|
||||
private const val T2E_OFFSET = 0x249F0
|
||||
private const val TEMP_C = 5797
|
||||
private val T2E = OfficialTables.T2E
|
||||
private val T2E274 = OfficialTables.T2E274
|
||||
|
||||
/**
|
||||
* Convert NUC counts to millidegrees C via the official T2E curve
|
||||
* (646-entry inverse map, Q12 interpolation).
|
||||
*/
|
||||
fun countsToTempMc(counts: Int): Int {
|
||||
var x = counts.toLong() * 3 - TEMP_C
|
||||
if (x < 0) x = 0
|
||||
var lo = 0
|
||||
var hi = 645
|
||||
while (lo < hi) {
|
||||
val mid = (lo + hi + 1) shr 1
|
||||
if (T2E[mid] <= x) lo = mid else hi = mid - 1
|
||||
}
|
||||
var i = lo
|
||||
if (i > 644) i = 644
|
||||
val diff = x - T2E[i]
|
||||
val t2 = (T2E[i + 1] - T2E[i]).toLong()
|
||||
val slope = if (t2 != 0L) (0x1000000L + t2 / 2) / t2 else 0L
|
||||
val temp = ((slope * diff) shr 12) + (i.toLong() shl 12) - T2E_OFFSET
|
||||
return temp.toInt()
|
||||
}
|
||||
|
||||
/**
|
||||
* T2E piecewise-linear evaluation with Q13 band selection
|
||||
* (vendor ReviseTemperature/CorrectTemperature core, 274-entry curve).
|
||||
*/
|
||||
fun t2eInterp(value: Int): Int {
|
||||
val v = value + 0xC350
|
||||
var idx = v shr 13
|
||||
if (idx < 0) idx = 0 else if (idx > T2E274.size - 2) idx = T2E274.size - 2
|
||||
val t0 = T2E274[idx]
|
||||
val t1 = T2E274[idx + 1]
|
||||
val d = v - (idx shl 13)
|
||||
val prod = d * (t1 - t0)
|
||||
var interp = prod shr 13
|
||||
if (interp < 0 && (prod and 0x1FFF) != 0) interp += 1
|
||||
return t0 + interp
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.mag160c.thermal.core
|
||||
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Differential test: the Kotlin render pipeline must produce byte-identical
|
||||
* output to the pixel-verified C implementation (csdk/src/mag160c_render.c),
|
||||
* which was validated against CoreSDKLib.dll. Reference files were produced
|
||||
* by analysis/render_offline.c with the official DDT + a synthetic frame
|
||||
* sequence (warm=20, ffc_period=1800, ffc_drift=250, cdf pivot 75, forced
|
||||
* frame-75 startup FFC, FFC callback noop).
|
||||
*/
|
||||
class RenderPipelineTest {
|
||||
private fun res(name: String): ByteArray =
|
||||
javaClass.classLoader.getResourceAsStream(name)!!.readBytes()
|
||||
|
||||
@Test
|
||||
fun matchesCReferencePixelExact() {
|
||||
val ddt = res("mag160c_official.ddt")
|
||||
val frames = res("test_frames.bin")
|
||||
val refRgb = res("test_ref.rgb")
|
||||
val refStats = res("test_ref_stats.txt").decodeToString()
|
||||
|
||||
val pipeline = RenderPipeline(
|
||||
w = 160, h = 120,
|
||||
ffcPeriod = 1800, ffcDrift = 250, warmFrames = 20,
|
||||
cdfPivot75 = true, force75 = true,
|
||||
onFfc = {},
|
||||
)
|
||||
assertTrue("DDT must load", pipeline.loadDdt(ddt))
|
||||
|
||||
val frameSize = 0x38 + 38400
|
||||
val nframes = frames.size / frameSize
|
||||
val out = IntArray(320 * 240)
|
||||
val renderedFrames = ArrayList<IntArray>()
|
||||
for (f in 0 until nframes) {
|
||||
val fr = frames.copyOfRange(f * frameSize, (f + 1) * frameSize)
|
||||
if (pipeline.frame(fr, true, out)) {
|
||||
renderedFrames.add(out.copyOf())
|
||||
}
|
||||
}
|
||||
|
||||
// reference: hidden frames end with "HIDDEN", rendered frames have 6 ints
|
||||
val refRendered = refStats.trim().lines().count { !it.endsWith("HIDDEN") }
|
||||
assertEquals("rendered frame count", refRendered, renderedFrames.size)
|
||||
|
||||
// per-pixel comparison (C writes RGB24; Kotlin produces the same ARGB ints)
|
||||
val rgb = res("test_ref.rgb")
|
||||
var off = 0
|
||||
for (fi in renderedFrames.indices) {
|
||||
val expect = IntArray(320 * 240)
|
||||
for (i in 0 until 320 * 240) {
|
||||
val r = refRgb[off].toInt() and 0xFF
|
||||
val g = refRgb[off + 1].toInt() and 0xFF
|
||||
val b = refRgb[off + 2].toInt() and 0xFF
|
||||
expect[i] = (0xFF shl 24) or (r shl 16) or (g shl 8) or b
|
||||
off += 3
|
||||
}
|
||||
assertArrayEquals("frame $fi pixels", expect, renderedFrames[fi])
|
||||
}
|
||||
assertEquals("consumed all reference bytes", refRgb.size, off)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun tempConversionIsMonotonicAndSane() {
|
||||
// protocol_spec: hand vs background ~1322 counts delta; 0x1000-ish counts
|
||||
// are ambient-scale. Sanity: monotonic and plausible millidegrees.
|
||||
var prev = Int.MIN_VALUE
|
||||
for (counts in 6000..8000 step 10) {
|
||||
val t = TempMath.countsToTempMc(counts)
|
||||
assertTrue("monotonic at $counts", t > prev)
|
||||
prev = t
|
||||
}
|
||||
// ~20-40 C around the 7000-count region (millidegrees)
|
||||
val t20 = TempMath.countsToTempMc(7000)
|
||||
assertTrue("temp in plausible range: $t20", t20 in 0..60_000)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun frameStreamReassemblesSplitReads() {
|
||||
val frames = res("test_frames.bin")
|
||||
val frameSize = 0x38 + 38400
|
||||
val stream = FrameStream(38400)
|
||||
val out = ByteArray(frameSize)
|
||||
var got = 0
|
||||
var pos = 0
|
||||
var seed = 7
|
||||
while (pos < frames.size) {
|
||||
seed = (seed * 1103515245 + 12345) and 0x7FFFFFFF
|
||||
val n = minOf(1 + (seed % 3000), frames.size - pos)
|
||||
val len = stream.push(frames.copyOfRange(pos, pos + n), n, out)
|
||||
if (len > 0) {
|
||||
for (i in 0 until frameSize) {
|
||||
assertEquals("byte $i at frame $got", frames[got * frameSize + i], out[i])
|
||||
}
|
||||
got++
|
||||
}
|
||||
pos += n
|
||||
}
|
||||
assertEquals("all frames reassembled", frames.size / frameSize, got)
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,60 @@
|
||||
0 HIDDEN
|
||||
1 HIDDEN
|
||||
2 HIDDEN
|
||||
3 HIDDEN
|
||||
4 HIDDEN
|
||||
5 HIDDEN
|
||||
6 HIDDEN
|
||||
7 HIDDEN
|
||||
8 HIDDEN
|
||||
9 HIDDEN
|
||||
10 HIDDEN
|
||||
11 HIDDEN
|
||||
12 HIDDEN
|
||||
13 HIDDEN
|
||||
14 HIDDEN
|
||||
15 HIDDEN
|
||||
16 HIDDEN
|
||||
17 HIDDEN
|
||||
18 HIDDEN
|
||||
19 HIDDEN
|
||||
20 HIDDEN
|
||||
21 HIDDEN
|
||||
22 HIDDEN
|
||||
23 HIDDEN
|
||||
24 HIDDEN
|
||||
25 HIDDEN
|
||||
26 HIDDEN
|
||||
27 HIDDEN
|
||||
28 HIDDEN
|
||||
29 HIDDEN
|
||||
30 HIDDEN
|
||||
31 HIDDEN
|
||||
32 6961 7961 7118 7668 7461 103
|
||||
33 6962 7962 7098 7663 7462 103
|
||||
34 6962 7962 7116 7675 7462 103
|
||||
35 6961 7961 7106 7672 7461 103
|
||||
36 6961 7961 7106 7659 7461 103
|
||||
37 6961 7961 7116 7664 7461 103
|
||||
38 6961 7961 7121 7671 7461 103
|
||||
39 6962 7962 7117 7673 7462 103
|
||||
40 6962 7962 7132 7667 7462 103
|
||||
41 6962 7962 7105 7664 7462 103
|
||||
42 6961 7961 7118 7671 7461 103
|
||||
43 6961 7961 7130 7671 7461 103
|
||||
44 6962 7962 7122 7661 7462 103
|
||||
45 6961 7961 7096 7669 7461 103
|
||||
46 6961 7961 7096 7671 7461 103
|
||||
47 6962 7962 7122 7675 7462 103
|
||||
48 6961 7961 7094 7667 7461 103
|
||||
49 6962 7962 7099 7664 7462 103
|
||||
50 6962 7962 7134 7669 7462 103
|
||||
51 6962 7962 7133 7662 7462 103
|
||||
52 6961 7961 7129 7671 7461 103
|
||||
53 6962 7962 7126 7671 7462 103
|
||||
54 6961 7961 7119 7669 7461 103
|
||||
55 6961 7961 7096 7673 7461 103
|
||||
56 6961 7961 7135 7661 7461 103
|
||||
57 6962 7962 7099 7662 7462 103
|
||||
58 6961 7961 7122 7659 7461 103
|
||||
59 6962 7962 7116 7669 7462 103
|
||||
@@ -0,0 +1,5 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.application) apply false
|
||||
alias(libs.plugins.kotlin.android) apply false
|
||||
alias(libs.plugins.kotlin.compose) apply false
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
org.gradle.caching=true
|
||||
android.useAndroidX=true
|
||||
kotlin.code.style=official
|
||||
@@ -0,0 +1,26 @@
|
||||
[versions]
|
||||
agp = "8.11.1"
|
||||
kotlin = "2.2.0"
|
||||
composeBom = "2025.06.01"
|
||||
activityCompose = "1.10.1"
|
||||
coreKtx = "1.16.0"
|
||||
lifecycle = "2.9.1"
|
||||
junit = "4.13.2"
|
||||
|
||||
[libraries]
|
||||
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
||||
compose-ui = { group = "androidx.compose.ui", name = "ui" }
|
||||
compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
|
||||
compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
|
||||
compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
|
||||
compose-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||
compose-material-icons = { group = "androidx.compose.material", name = "material-icons-extended" }
|
||||
activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
||||
core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" }
|
||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
Vendored
+251
@@ -0,0 +1,251 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH="\\\"\\\""
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
Vendored
+94
@@ -0,0 +1,94 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -0,0 +1,16 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
rootProject.name = "MAG160C"
|
||||
include(":app")
|
||||
Reference in New Issue
Block a user