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.
@@ -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 = listOf("iron", "rainbow", "grayscale", "arctic", "lava", "contrast", "hotcold", "medical")
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> = emptyList()
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 with modular loop
442
- val palettes = listOf("iron", "rainbow", "grayscale", "arctic", "lava", "contrast", "hotcold", "medical")
443
- val paletteIdx = rawIdx % palettes.size
444
- val safeIdx = if (paletteIdx < 0) paletteIdx + palettes.size else paletteIdx
445
- sdkManager?.setPalette(palettes[safeIdx])
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 = "iron";
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
- Palette palette =
360
- PaletteManager.getDefaultPalettes().stream()
361
- .filter(p -> p.name.equalsIgnoreCase(paletteToApply))
362
- .findFirst()
363
- .orElse(null);
364
- if (palette != null) {
365
- thermalImage.setPalette(palette);
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 = "iron"
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 (0..7) to palette names
519
- let palettes = ["iron", "rainbow", "grayscale", "arctic", "lava", "contrast", "hotcold", "medical"]
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilabs-flir",
3
- "version": "2.3.2",
3
+ "version": "2.3.4",
4
4
  "description": "FLIR Thermal SDK for React Native - iOS & Android (bundled at compile time via postinstall)",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
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>;