ilabs-flir 2.3.2 → 2.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/Flir/src/main/java/flir/android/FlirManager.kt +103 -9
- package/android/Flir/src/main/java/flir/android/FlirModule.kt +30 -0
- package/android/Flir/src/main/java/flir/android/FlirSdkManager.java +18 -8
- package/ios/Flir/src/FlirManager.swift +73 -3
- package/ios/Flir/src/FlirModule.m +24 -0
- package/package.json +1 -1
- package/src/index.d.ts +6 -0
|
@@ -10,6 +10,7 @@ import com.facebook.react.bridge.WritableMap
|
|
|
10
10
|
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
11
11
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
12
12
|
import com.flir.thermalsdk.live.Identity
|
|
13
|
+
import com.flir.thermalsdk.image.PaletteManager
|
|
13
14
|
import java.io.File
|
|
14
15
|
import java.io.FileOutputStream
|
|
15
16
|
import java.util.concurrent.atomic.AtomicLong
|
|
@@ -75,16 +76,30 @@ object FlirManager {
|
|
|
75
76
|
sdkManager?.setPalette(name)
|
|
76
77
|
// Also try to update the app's global Var.cool if possible
|
|
77
78
|
try {
|
|
78
|
-
val palettes =
|
|
79
|
+
val palettes = getAvailablePalettes()
|
|
79
80
|
val idx = palettes.indexOf(name.lowercase())
|
|
80
81
|
if (idx != -1) {
|
|
81
|
-
// If we found the index, we use the raw value.
|
|
82
|
-
// But if the name itself is passed as a number or from a loop, handle it.
|
|
83
82
|
updateAcol(idx.toFloat())
|
|
84
83
|
}
|
|
85
84
|
} catch (e: Exception) {}
|
|
86
85
|
}
|
|
87
|
-
fun getAvailablePalettes(): List<String>
|
|
86
|
+
fun getAvailablePalettes(): List<String> {
|
|
87
|
+
return try {
|
|
88
|
+
val palettes = PaletteManager.getDefaultPalettes().map { it.name }.toMutableList()
|
|
89
|
+
// Move WhiteHot/Gray to the front
|
|
90
|
+
val grayIdx = palettes.indexOfFirst {
|
|
91
|
+
it.equals("WhiteHot", ignoreCase = true) || it.equals("Gray", ignoreCase = true) || it.contains("gray", ignoreCase = true)
|
|
92
|
+
}
|
|
93
|
+
if (grayIdx > 0) {
|
|
94
|
+
val gray = palettes.removeAt(grayIdx)
|
|
95
|
+
palettes.add(0, gray)
|
|
96
|
+
}
|
|
97
|
+
palettes
|
|
98
|
+
} catch (t: Throwable) {
|
|
99
|
+
Log.e(TAG, "Failed to get available palettes from SDK", t)
|
|
100
|
+
listOf("grayscale", "iron", "rainbow", "arctic", "lava", "contrast", "hotcold", "medical")
|
|
101
|
+
}
|
|
102
|
+
}
|
|
88
103
|
|
|
89
104
|
/**
|
|
90
105
|
* Initialize the FLIR SDK
|
|
@@ -102,6 +117,15 @@ object FlirManager {
|
|
|
102
117
|
|
|
103
118
|
isInitialized = true
|
|
104
119
|
Log.i(TAG, "FlirManager initialized")
|
|
120
|
+
|
|
121
|
+
// Generate palette icons on background thread
|
|
122
|
+
Thread {
|
|
123
|
+
try {
|
|
124
|
+
generatePaletteIcons(context)
|
|
125
|
+
} catch (e: Exception) {
|
|
126
|
+
Log.e(TAG, "Initial palette icon generation failed", e)
|
|
127
|
+
}
|
|
128
|
+
}.start()
|
|
105
129
|
}
|
|
106
130
|
|
|
107
131
|
/**
|
|
@@ -438,15 +462,85 @@ object FlirManager {
|
|
|
438
462
|
coolField.set(null, shaderIdx)
|
|
439
463
|
Log.d(TAG, "Updated Var.cool to $shaderIdx via reflection (raw=$rawIdx)")
|
|
440
464
|
|
|
441
|
-
// If we are in FLIR mode, also notify the SDK palette
|
|
442
|
-
val
|
|
443
|
-
val
|
|
444
|
-
|
|
445
|
-
|
|
465
|
+
// If we are in FLIR mode, also notify the SDK palette using the official SDK palette list
|
|
466
|
+
val sdkPalettes = PaletteManager.getDefaultPalettes()
|
|
467
|
+
val paletteNames = sdkPalettes.map { it.name }
|
|
468
|
+
Log.d(TAG, "True FLIR Palettes: $paletteNames")
|
|
469
|
+
|
|
470
|
+
val maxEff = sdkPalettes.size
|
|
471
|
+
val paletteIdx = rawIdx % maxEff
|
|
472
|
+
val safeIdx = if (paletteIdx < 0) paletteIdx + maxEff else paletteIdx
|
|
473
|
+
|
|
474
|
+
val targetPaletteName = paletteNames[safeIdx]
|
|
475
|
+
Log.d(TAG, "Applying true FLIR palette: $targetPaletteName (index: $safeIdx, max: $maxEff)")
|
|
476
|
+
|
|
477
|
+
sdkManager?.setPalette(targetPaletteName)
|
|
446
478
|
|
|
447
479
|
} catch (e: Exception) {
|
|
448
480
|
Log.w(TAG, "Could not update Var.cool via reflection: ${e.message}")
|
|
449
481
|
}
|
|
450
482
|
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Generate icons for all default palettes and save to cache.
|
|
486
|
+
*/
|
|
487
|
+
fun generatePaletteIcons(context: Context): List<Map<String, String>> {
|
|
488
|
+
val results = mutableListOf<Map<String, String>>()
|
|
489
|
+
try {
|
|
490
|
+
val rawPalettes = PaletteManager.getDefaultPalettes()
|
|
491
|
+
val palettes = rawPalettes.toMutableList()
|
|
492
|
+
|
|
493
|
+
// Reorder: Move WhiteHot/Gray to the front
|
|
494
|
+
val grayIdx = palettes.indexOfFirst {
|
|
495
|
+
it.name.equals("WhiteHot", ignoreCase = true) || it.name.equals("Gray", ignoreCase = true) || it.name.contains("gray", ignoreCase = true)
|
|
496
|
+
}
|
|
497
|
+
if (grayIdx > 0) {
|
|
498
|
+
val gray = palettes.removeAt(grayIdx)
|
|
499
|
+
palettes.add(0, gray)
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
val dir = File(context.cacheDir, "flir_palettes")
|
|
503
|
+
if (!dir.exists()) dir.mkdirs()
|
|
504
|
+
|
|
505
|
+
for (palette in palettes) {
|
|
506
|
+
val iconFile = File(dir, "${palette.name.lowercase()}.png")
|
|
507
|
+
if (!iconFile.exists()) {
|
|
508
|
+
try {
|
|
509
|
+
// Common method names for palette preview in FLIR SDK: getBitmap(), getIcon()
|
|
510
|
+
val bitmap = try {
|
|
511
|
+
val m = palette.javaClass.getMethod("getBitmap")
|
|
512
|
+
m.invoke(palette) as? Bitmap
|
|
513
|
+
} catch (e: Exception) {
|
|
514
|
+
try {
|
|
515
|
+
val m = palette.javaClass.getMethod("getIcon")
|
|
516
|
+
m.invoke(palette) as? Bitmap
|
|
517
|
+
} catch (e2: Exception) { null }
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
bitmap?.let {
|
|
521
|
+
FileOutputStream(iconFile).use { out ->
|
|
522
|
+
it.compress(Bitmap.CompressFormat.PNG, 100, out)
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
} catch (e: Exception) {
|
|
526
|
+
Log.w(TAG, "Failed to save icon for ${palette.name}: ${e.message}")
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
results.add(mapOf(
|
|
530
|
+
"name" to palette.name,
|
|
531
|
+
"uri" to if (iconFile.exists()) "file://${iconFile.absolutePath}" else ""
|
|
532
|
+
))
|
|
533
|
+
}
|
|
534
|
+
} catch (t: Throwable) {
|
|
535
|
+
Log.e(TAG, "Palette icon generation error", t)
|
|
536
|
+
}
|
|
537
|
+
return results
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
fun getPalettesWithIcons(context: Context? = null): List<Map<String, String>> {
|
|
541
|
+
val ctx = context ?: reactContext ?: return emptyList()
|
|
542
|
+
return generatePaletteIcons(ctx)
|
|
543
|
+
}
|
|
544
|
+
|
|
451
545
|
fun destroy() { stop() }
|
|
452
546
|
}
|
|
@@ -137,6 +137,36 @@ class FlirModule(private val reactContext: ReactApplicationContext) : ReactConte
|
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
@ReactMethod
|
|
141
|
+
fun getAvailablePalettes(promise: Promise?) {
|
|
142
|
+
try {
|
|
143
|
+
val palettes = FlirManager.getAvailablePalettes()
|
|
144
|
+
val result = com.facebook.react.bridge.Arguments.createArray()
|
|
145
|
+
palettes.forEach { result.pushString(it) }
|
|
146
|
+
promise?.resolve(result)
|
|
147
|
+
} catch (e: Exception) {
|
|
148
|
+
promise?.reject("ERR_FLIR_PALETTES", e)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
@ReactMethod
|
|
153
|
+
fun getPalettesWithIcons(promise: Promise?) {
|
|
154
|
+
try {
|
|
155
|
+
val palettes = FlirManager.getPalettesWithIcons(reactContext)
|
|
156
|
+
val result = com.facebook.react.bridge.Arguments.createArray()
|
|
157
|
+
|
|
158
|
+
palettes.forEach { p ->
|
|
159
|
+
val map = com.facebook.react.bridge.Arguments.createMap()
|
|
160
|
+
map.putString("name", p["name"])
|
|
161
|
+
map.putString("uri", p["uri"])
|
|
162
|
+
result.pushMap(map)
|
|
163
|
+
}
|
|
164
|
+
promise?.resolve(result)
|
|
165
|
+
} catch (e: Exception) {
|
|
166
|
+
promise?.reject("ERR_FLIR_PALETTE_ICONS", e)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
140
170
|
@ReactMethod
|
|
141
171
|
fun getDiscoveredDevices(promise: Promise?) {
|
|
142
172
|
try {
|
|
@@ -51,7 +51,7 @@ public class FlirSdkManager {
|
|
|
51
51
|
private Stream activeStream;
|
|
52
52
|
private final List<Identity> discoveredDevices = Collections.synchronizedList(new ArrayList<>());
|
|
53
53
|
private volatile Bitmap latestBitmap;
|
|
54
|
-
private volatile String currentPaletteName = "
|
|
54
|
+
private volatile String currentPaletteName = "WhiteHot";
|
|
55
55
|
private final AtomicBoolean isProcessingFrame = new AtomicBoolean(false);
|
|
56
56
|
private boolean useHalfScale = false;
|
|
57
57
|
private String pendingSnapshotPath = null;
|
|
@@ -356,13 +356,23 @@ public class FlirSdkManager {
|
|
|
356
356
|
streamer.withThermalImage(thermalImage -> {
|
|
357
357
|
// 1. Apply Palette
|
|
358
358
|
if (paletteToApply != null) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
359
|
+
try {
|
|
360
|
+
Palette palette = null;
|
|
361
|
+
// Try modern way if available (via image's palette manager)
|
|
362
|
+
// Otherwise fallback to static PaletteManager
|
|
363
|
+
List<Palette> sdkPalettes = PaletteManager.getDefaultPalettes();
|
|
364
|
+
for (Palette p : sdkPalettes) {
|
|
365
|
+
if (p.name.equalsIgnoreCase(paletteToApply)) {
|
|
366
|
+
palette = p;
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (palette != null) {
|
|
372
|
+
thermalImage.setPalette(palette);
|
|
373
|
+
}
|
|
374
|
+
} catch (Throwable t) {
|
|
375
|
+
Log.e(TAG, "Failed to apply palette: " + paletteToApply, t);
|
|
366
376
|
}
|
|
367
377
|
}
|
|
368
378
|
|
|
@@ -67,7 +67,7 @@ import ThermalSDK
|
|
|
67
67
|
private let stateLock = NSObject()
|
|
68
68
|
|
|
69
69
|
// Palette and Snapshot state
|
|
70
|
-
private var currentPaletteName: String = "
|
|
70
|
+
private var currentPaletteName: String = "WhiteHot"
|
|
71
71
|
private var pendingSnapshotPath: String?
|
|
72
72
|
|
|
73
73
|
// Dedicated render queue for frame processing (matches sample app pattern)
|
|
@@ -91,6 +91,11 @@ import ThermalSDK
|
|
|
91
91
|
private override init() {
|
|
92
92
|
super.init()
|
|
93
93
|
NSLog("[FlirManager] Initialized")
|
|
94
|
+
|
|
95
|
+
// Generate palette icons on background thread
|
|
96
|
+
DispatchQueue.global().async {
|
|
97
|
+
self.generatePaletteIcons()
|
|
98
|
+
}
|
|
94
99
|
}
|
|
95
100
|
|
|
96
101
|
// MARK: - Public State
|
|
@@ -515,14 +520,79 @@ import ThermalSDK
|
|
|
515
520
|
}
|
|
516
521
|
|
|
517
522
|
@objc public func setPaletteFromAcol(_ acol: Float) {
|
|
518
|
-
// Map acol
|
|
519
|
-
let palettes =
|
|
523
|
+
// Map acol to dynamic palette names
|
|
524
|
+
let palettes = getAvailablePalettes()
|
|
520
525
|
let idx = Int(acol)
|
|
521
526
|
if idx >= 0 && idx < palettes.count {
|
|
522
527
|
setPalette(palettes[idx])
|
|
523
528
|
}
|
|
524
529
|
}
|
|
525
530
|
|
|
531
|
+
@objc public func getAvailablePalettes() -> [String] {
|
|
532
|
+
#if FLIR_ENABLED
|
|
533
|
+
if let streamer = streamer {
|
|
534
|
+
var names: [String] = []
|
|
535
|
+
streamer.withThermalImage { img in
|
|
536
|
+
var palettes = img.paletteManager.getDefaultPalettes().map { $0.name }
|
|
537
|
+
// Reorder: Move WhiteHot/Gray to the front
|
|
538
|
+
if let grayIdx = palettes.firstIndex(where: { $0.lowercased() == "whitehot" || $0.lowercased() == "gray" }) {
|
|
539
|
+
let gray = palettes.remove(at: grayIdx)
|
|
540
|
+
palettes.insert(gray, at: 0)
|
|
541
|
+
}
|
|
542
|
+
names = palettes
|
|
543
|
+
}
|
|
544
|
+
if !names.isEmpty { return names }
|
|
545
|
+
}
|
|
546
|
+
#endif
|
|
547
|
+
return ["WhiteHot", "iron", "rainbow", "arctic", "lava", "contrast", "hotcold", "medical"]
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
@objc public func generatePaletteIcons() -> [[String: String]] {
|
|
551
|
+
var results: [[String: String]] = []
|
|
552
|
+
#if FLIR_ENABLED
|
|
553
|
+
guard let streamer = streamer else { return [] }
|
|
554
|
+
|
|
555
|
+
let fileManager = FileManager.default
|
|
556
|
+
let cacheDir = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!
|
|
557
|
+
let paletteDir = cacheDir.appendingPathComponent("flir_palettes")
|
|
558
|
+
|
|
559
|
+
if !fileManager.fileExists(atPath: paletteDir.path) {
|
|
560
|
+
try? fileManager.createDirectory(at: paletteDir, withIntermediateDirectories: true)
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
streamer.withThermalImage { thermalImage in
|
|
564
|
+
var palettes = thermalImage.paletteManager.getDefaultPalettes()
|
|
565
|
+
|
|
566
|
+
// Reorder: Move WhiteHot/Gray to the front
|
|
567
|
+
if let grayIdx = palettes.firstIndex(where: { $0.name.lowercased() == "whitehot" || $0.name.lowercased() == "gray" }) {
|
|
568
|
+
let gray = palettes.remove(at: grayIdx)
|
|
569
|
+
palettes.insert(gray, at: 0)
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
for palette in palettes {
|
|
573
|
+
let iconURL = paletteDir.appendingPathComponent("\(palette.name.lowercased()).png")
|
|
574
|
+
if !fileManager.fileExists(atPath: iconURL.path) {
|
|
575
|
+
// Assuming palette.icon exists (common in FLIR SDK)
|
|
576
|
+
if let icon = palette.icon {
|
|
577
|
+
if let data = icon.pngData() {
|
|
578
|
+
try? data.write(to: iconURL)
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
results.append([
|
|
583
|
+
"name": palette.name,
|
|
584
|
+
"uri": fileManager.fileExists(atPath: iconURL.path) ? iconURL.absoluteString : ""
|
|
585
|
+
])
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
#endif
|
|
589
|
+
return results
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
@objc public func getPalettesWithIcons() -> [[String: String]] {
|
|
593
|
+
return generatePaletteIcons()
|
|
594
|
+
}
|
|
595
|
+
|
|
526
596
|
@objc public func captureRadiometricSnapshot(_ path: String) {
|
|
527
597
|
self.pendingSnapshotPath = path
|
|
528
598
|
NSLog("[FlirManager] Pending radiometric snapshot: \(path)")
|
|
@@ -466,6 +466,30 @@ RCT_EXPORT_METHOD(isBatteryCharging : (RCTPromiseResolveBlock)
|
|
|
466
466
|
});
|
|
467
467
|
}
|
|
468
468
|
|
|
469
|
+
RCT_EXPORT_METHOD(getAvailablePalettes : (RCTPromiseResolveBlock)
|
|
470
|
+
resolve rejecter : (RCTPromiseRejectBlock)reject) {
|
|
471
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
472
|
+
id manager = flir_manager_shared();
|
|
473
|
+
NSArray *palettes = @[];
|
|
474
|
+
if (manager && [manager respondsToSelector:sel_registerName("getAvailablePalettes")]) {
|
|
475
|
+
palettes = ((NSArray * (*)(id, SEL)) objc_msgSend)(manager, sel_registerName("getAvailablePalettes"));
|
|
476
|
+
}
|
|
477
|
+
if (resolve) resolve(palettes);
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
RCT_EXPORT_METHOD(getPalettesWithIcons : (RCTPromiseResolveBlock)
|
|
482
|
+
resolve rejecter : (RCTPromiseRejectBlock)reject) {
|
|
483
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
484
|
+
id manager = flir_manager_shared();
|
|
485
|
+
NSArray *palettes = @[];
|
|
486
|
+
if (manager && [manager respondsToSelector:sel_registerName("getPalettesWithIcons")]) {
|
|
487
|
+
palettes = ((NSArray * (*)(id, SEL)) objc_msgSend)(manager, sel_registerName("getPalettesWithIcons"));
|
|
488
|
+
}
|
|
489
|
+
if (resolve) resolve(palettes);
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
469
493
|
RCT_EXPORT_METHOD(setPreferSdkRotation : (BOOL)prefer resolver : (
|
|
470
494
|
RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) {
|
|
471
495
|
dispatch_async(dispatch_get_main_queue(), ^{
|
package/package.json
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -54,6 +54,12 @@ export interface FlirModuleAPI {
|
|
|
54
54
|
stopFlir(): Promise<boolean>;
|
|
55
55
|
getDiscoveredDevices(): Promise<FlirDevice[]>;
|
|
56
56
|
|
|
57
|
+
// Palette & Imaging APIs
|
|
58
|
+
getAvailablePalettes(): Promise<string[]>;
|
|
59
|
+
getPalettesWithIcons(): Promise<{ name: string; uri: string }[]>;
|
|
60
|
+
setPalette(name: string): Promise<boolean>;
|
|
61
|
+
captureRadiometricSnapshot(path: string): Promise<boolean>;
|
|
62
|
+
|
|
57
63
|
// Debug APIs
|
|
58
64
|
initializeSDK(): Promise<SDKInitResult>;
|
|
59
65
|
getDebugInfo(): Promise<FlirDebugInfo>;
|