ilabs-flir 2.4.10 → 2.4.12
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/Flir.podspec
CHANGED
|
@@ -64,6 +64,7 @@ object FlirManager {
|
|
|
64
64
|
|
|
65
65
|
fun setTextureCallback(callback: TextureUpdateCallback?) {
|
|
66
66
|
textureCallback = callback
|
|
67
|
+
sdkManager?.setFrameConsumerActive(callback != null)
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
interface TemperatureUpdateCallback {
|
|
@@ -107,6 +108,7 @@ object FlirManager {
|
|
|
107
108
|
sdkManager = FlirSdkManager.getInstance(context)
|
|
108
109
|
sdkManager?.setListener(sdkListener)
|
|
109
110
|
sdkManager?.initialize()
|
|
111
|
+
sdkManager?.setFrameConsumerActive(textureCallback != null)
|
|
110
112
|
|
|
111
113
|
isInitialized = true
|
|
112
114
|
Log.i(TAG, "FlirManager initialized")
|
|
@@ -56,6 +56,10 @@ public class FlirSdkManager {
|
|
|
56
56
|
private final List<Identity> discoveredDevices = Collections.synchronizedList(new ArrayList<>());
|
|
57
57
|
private final Map<String, DiscoveredCamera> discoveredCameras = Collections.synchronizedMap(new HashMap<>());
|
|
58
58
|
private volatile Bitmap latestBitmap;
|
|
59
|
+
private final Bitmap[] bitmapRingBuffer = new Bitmap[3];
|
|
60
|
+
private int ringBufferIndex = 0;
|
|
61
|
+
private volatile boolean isFrameConsumerActive = false;
|
|
62
|
+
private int[] scalePixelBuffer = null;
|
|
59
63
|
private volatile String currentPaletteName = "WhiteHot";
|
|
60
64
|
private final AtomicBoolean isProcessingFrame = new AtomicBoolean(false);
|
|
61
65
|
private boolean useHalfScale = false;
|
|
@@ -118,6 +122,22 @@ public class FlirSdkManager {
|
|
|
118
122
|
}
|
|
119
123
|
}
|
|
120
124
|
};
|
|
125
|
+
|
|
126
|
+
// Auto-detect low-end device to enable useHalfScale
|
|
127
|
+
try {
|
|
128
|
+
android.app.ActivityManager.MemoryInfo mi = new android.app.ActivityManager.MemoryInfo();
|
|
129
|
+
android.app.ActivityManager activityManager = (android.app.ActivityManager) this.context.getSystemService(Context.ACTIVITY_SERVICE);
|
|
130
|
+
if (activityManager != null) {
|
|
131
|
+
activityManager.getMemoryInfo(mi);
|
|
132
|
+
boolean isLowRam = activityManager.isLowRamDevice();
|
|
133
|
+
if (isLowRam || mi.totalMem < 3L * 1024 * 1024 * 1024) {
|
|
134
|
+
useHalfScale = true;
|
|
135
|
+
Log.i(TAG, "Low-end device detected (Total RAM: " + (mi.totalMem / (1024 * 1024)) + "MB). Enabling half-scale rendering by default.");
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} catch (Throwable t) {
|
|
139
|
+
Log.e(TAG, "Failed to auto-detect memory constraints", t);
|
|
140
|
+
}
|
|
121
141
|
}
|
|
122
142
|
|
|
123
143
|
public static synchronized FlirSdkManager getInstance(Context context) {
|
|
@@ -145,6 +165,25 @@ public class FlirSdkManager {
|
|
|
145
165
|
try {
|
|
146
166
|
android.os.Looper.loop();
|
|
147
167
|
} catch (Throwable t) {
|
|
168
|
+
if (t instanceof VirtualMachineError) {
|
|
169
|
+
Log.e(TAG, "🚨 [FLIR LOOPER PROTECTOR] VirtualMachineError/OOM detected! Releasing display bitmap and running GC...");
|
|
170
|
+
try {
|
|
171
|
+
if (instance != null) {
|
|
172
|
+
synchronized (instance) {
|
|
173
|
+
for (int i = 0; i < 3; i++) {
|
|
174
|
+
if (instance.bitmapRingBuffer[i] != null) {
|
|
175
|
+
instance.bitmapRingBuffer[i].recycle();
|
|
176
|
+
instance.bitmapRingBuffer[i] = null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
instance.latestBitmap = null;
|
|
180
|
+
instance.scalePixelBuffer = null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
System.gc();
|
|
184
|
+
} catch (Throwable ignored) {}
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
148
187
|
String msg = t.getMessage();
|
|
149
188
|
boolean isSuppressed = false;
|
|
150
189
|
|
|
@@ -425,6 +464,11 @@ public class FlirSdkManager {
|
|
|
425
464
|
});
|
|
426
465
|
}
|
|
427
466
|
|
|
467
|
+
public void setFrameConsumerActive(boolean active) {
|
|
468
|
+
this.isFrameConsumerActive = active;
|
|
469
|
+
Log.d(TAG, "isFrameConsumerActive set to: " + active);
|
|
470
|
+
}
|
|
471
|
+
|
|
428
472
|
private void startStreamInternal() {
|
|
429
473
|
if (camera == null) {
|
|
430
474
|
notifyError("Not connected");
|
|
@@ -504,103 +548,164 @@ public class FlirSdkManager {
|
|
|
504
548
|
// to ensure the native frame reference remains valid.
|
|
505
549
|
if (isProcessingFrame.compareAndSet(false, true)) {
|
|
506
550
|
try {
|
|
507
|
-
|
|
508
|
-
streamer
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
551
|
+
synchronized (FlirSdkManager.this) {
|
|
552
|
+
if (streamer != null && activeStream != null) {
|
|
553
|
+
streamer.update();
|
|
554
|
+
final String paletteToApply = currentPaletteName;
|
|
555
|
+
final String snapshotPath = pendingSnapshotPath;
|
|
556
|
+
pendingSnapshotPath = null;
|
|
557
|
+
streamer.withThermalImage(thermalImage -> {
|
|
558
|
+
if (!isFrameConsumerActive && snapshotPath == null) {
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
// 1. Apply Palette
|
|
562
|
+
if (paletteToApply != null) {
|
|
563
|
+
try {
|
|
564
|
+
List<Palette> sdkPalettes = cachedSdkPalettes;
|
|
565
|
+
if (sdkPalettes == null) {
|
|
566
|
+
synchronized (FlirSdkManager.this) {
|
|
567
|
+
sdkPalettes = cachedSdkPalettes;
|
|
568
|
+
if (sdkPalettes == null) {
|
|
569
|
+
try {
|
|
570
|
+
sdkPalettes = PaletteManager.getDefaultPalettes();
|
|
571
|
+
cachedSdkPalettes = sdkPalettes;
|
|
572
|
+
} catch (Throwable t) {
|
|
573
|
+
Log.e(TAG, "Failed to get default palettes", t);
|
|
574
|
+
}
|
|
527
575
|
}
|
|
528
576
|
}
|
|
529
577
|
}
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
if (paletteToApply.equalsIgnoreCase("Gray") || paletteToApply.equalsIgnoreCase("grayscale")) {
|
|
533
|
-
// User wants Gray - map to WhiteHot which is the SDK's standard grayscale
|
|
534
|
-
for (Palette p : sdkPalettes) {
|
|
535
|
-
if (p.name.equalsIgnoreCase("WhiteHot") || p.name.equalsIgnoreCase("White hot")) {
|
|
536
|
-
thermalImage.setPalette(p);
|
|
537
|
-
break;
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
} else {
|
|
541
|
-
Palette palette = null;
|
|
542
|
-
for (Palette p : sdkPalettes) {
|
|
543
|
-
if (p.name.equalsIgnoreCase(paletteToApply)) {
|
|
544
|
-
palette = p;
|
|
545
|
-
break;
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
578
|
|
|
549
|
-
if (
|
|
550
|
-
|
|
551
|
-
} else if (paletteToApply.equalsIgnoreCase("Wheel")) {
|
|
552
|
-
// Fallback for Wheel if not found - some SDKs use different names
|
|
579
|
+
if (paletteToApply.equalsIgnoreCase("Gray") || paletteToApply.equalsIgnoreCase("grayscale")) {
|
|
580
|
+
// User wants Gray - map to WhiteHot which is the SDK's standard grayscale
|
|
553
581
|
for (Palette p : sdkPalettes) {
|
|
554
|
-
if (p.name.
|
|
582
|
+
if (p.name.equalsIgnoreCase("WhiteHot") || p.name.equalsIgnoreCase("White hot")) {
|
|
555
583
|
thermalImage.setPalette(p);
|
|
556
584
|
break;
|
|
557
585
|
}
|
|
558
586
|
}
|
|
587
|
+
} else {
|
|
588
|
+
Palette palette = null;
|
|
589
|
+
for (Palette p : sdkPalettes) {
|
|
590
|
+
if (p.name.equalsIgnoreCase(paletteToApply)) {
|
|
591
|
+
palette = p;
|
|
592
|
+
break;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (palette != null) {
|
|
597
|
+
thermalImage.setPalette(palette);
|
|
598
|
+
} else if (paletteToApply.equalsIgnoreCase("Wheel")) {
|
|
599
|
+
// Fallback for Wheel if not found - some SDKs use different names
|
|
600
|
+
for (Palette p : sdkPalettes) {
|
|
601
|
+
if (p.name.contains("Wheel") || p.name.contains("ColorWheel") || p.name.contains("Rainbow")) {
|
|
602
|
+
thermalImage.setPalette(p);
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
559
607
|
}
|
|
608
|
+
} catch (Throwable t) {
|
|
609
|
+
Log.e(TAG, "Failed to apply palette: " + paletteToApply, t);
|
|
560
610
|
}
|
|
561
|
-
} catch (Throwable t) {
|
|
562
|
-
Log.e(TAG, "Failed to apply palette: " + paletteToApply, t);
|
|
563
611
|
}
|
|
564
|
-
}
|
|
565
612
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
613
|
+
// 2. Save Radiometric Snapshot if requested
|
|
614
|
+
if (snapshotPath != null) {
|
|
615
|
+
try {
|
|
616
|
+
Log.i(TAG, "[SNAPSHOT] Attempting to save radiometric snapshot: " + snapshotPath);
|
|
617
|
+
thermalImage.saveAs(snapshotPath);
|
|
618
|
+
Log.i(TAG, "[SNAPSHOT] ✅ Success: Radiometric snapshot saved");
|
|
619
|
+
if (snapshotCallback != null) {
|
|
620
|
+
snapshotCallback.onSnapshotSaved(snapshotPath);
|
|
621
|
+
snapshotCallback = null;
|
|
622
|
+
}
|
|
623
|
+
} catch (java.io.IOException e) {
|
|
624
|
+
Log.e(TAG, "Failed to save radiometric snapshot", e);
|
|
625
|
+
if (snapshotCallback != null) {
|
|
626
|
+
snapshotCallback.onSnapshotError(e.getMessage());
|
|
627
|
+
snapshotCallback = null;
|
|
628
|
+
}
|
|
581
629
|
}
|
|
582
630
|
}
|
|
583
|
-
}
|
|
584
631
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
632
|
+
// 3. Generate Bitmap for display
|
|
633
|
+
if (isFrameConsumerActive) {
|
|
634
|
+
try {
|
|
635
|
+
ImageBuffer imageBuffer = streamer.getImage();
|
|
636
|
+
if (imageBuffer != null) {
|
|
637
|
+
int width = imageBuffer.getWidth();
|
|
638
|
+
int height = imageBuffer.getHeight();
|
|
639
|
+
if (width > 0 && height > 0) {
|
|
640
|
+
synchronized (FlirSdkManager.this) {
|
|
641
|
+
int dstW = useHalfScale ? (width / 2) : width;
|
|
642
|
+
int dstH = useHalfScale ? (height / 2) : height;
|
|
643
|
+
|
|
644
|
+
for (int i = 0; i < 3; i++) {
|
|
645
|
+
if (bitmapRingBuffer[i] == null ||
|
|
646
|
+
bitmapRingBuffer[i].getWidth() != dstW ||
|
|
647
|
+
bitmapRingBuffer[i].getHeight() != dstH) {
|
|
648
|
+
|
|
649
|
+
if (bitmapRingBuffer[i] != null) {
|
|
650
|
+
bitmapRingBuffer[i].recycle();
|
|
651
|
+
}
|
|
652
|
+
bitmapRingBuffer[i] = Bitmap.createBitmap(dstW, dstH, Bitmap.Config.ARGB_8888);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
ringBufferIndex = (ringBufferIndex + 1) % 3;
|
|
656
|
+
Bitmap targetBitmap = bitmapRingBuffer[ringBufferIndex];
|
|
657
|
+
|
|
658
|
+
if (useHalfScale) {
|
|
659
|
+
imageBuffer.with(new com.flir.thermalsdk.utils.Consumer<java.nio.ByteBuffer>() {
|
|
660
|
+
@Override
|
|
661
|
+
public void accept(java.nio.ByteBuffer byteBuffer) {
|
|
662
|
+
if (byteBuffer != null) {
|
|
663
|
+
byteBuffer.rewind();
|
|
664
|
+
java.nio.IntBuffer srcPixels = byteBuffer.asIntBuffer();
|
|
665
|
+
|
|
666
|
+
int totalPixels = dstW * dstH;
|
|
667
|
+
if (scalePixelBuffer == null || scalePixelBuffer.length != totalPixels) {
|
|
668
|
+
scalePixelBuffer = new int[totalPixels];
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
for (int y = 0; y < dstH; y++) {
|
|
672
|
+
int srcY = y * 2;
|
|
673
|
+
int srcRowOffset = srcY * width;
|
|
674
|
+
int dstRowOffset = y * dstW;
|
|
675
|
+
for (int x = 0; x < dstW; x++) {
|
|
676
|
+
int srcX = x * 2;
|
|
677
|
+
scalePixelBuffer[dstRowOffset + x] = srcPixels.get(srcRowOffset + srcX);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
targetBitmap.setPixels(scalePixelBuffer, 0, dstW, 0, 0, dstW, dstH);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
} else {
|
|
685
|
+
imageBuffer.with(new com.flir.thermalsdk.utils.Consumer<java.nio.ByteBuffer>() {
|
|
686
|
+
@Override
|
|
687
|
+
public void accept(java.nio.ByteBuffer byteBuffer) {
|
|
688
|
+
if (byteBuffer != null) {
|
|
689
|
+
byteBuffer.rewind();
|
|
690
|
+
targetBitmap.copyPixelsFromBuffer(byteBuffer);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
latestBitmap = targetBitmap;
|
|
697
|
+
if (listener != null) {
|
|
698
|
+
listener.onFrame(targetBitmap);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
} catch (Exception e) {
|
|
704
|
+
Log.e(TAG, "Bitmap buffer copy failed", e);
|
|
598
705
|
}
|
|
599
706
|
}
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
}
|
|
603
|
-
});
|
|
707
|
+
});
|
|
708
|
+
}
|
|
604
709
|
}
|
|
605
710
|
} catch (Exception e) {
|
|
606
711
|
Log.e(TAG, "Frame processing error", e);
|
|
@@ -629,16 +734,25 @@ public class FlirSdkManager {
|
|
|
629
734
|
}
|
|
630
735
|
|
|
631
736
|
private void stopStreamInternal() {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
737
|
+
synchronized (this) {
|
|
738
|
+
if (activeStream != null) {
|
|
739
|
+
try {
|
|
740
|
+
activeStream.stop();
|
|
741
|
+
} catch (Exception e) {
|
|
742
|
+
Log.e(TAG, "Stop stream error", e);
|
|
743
|
+
}
|
|
744
|
+
activeStream = null;
|
|
745
|
+
}
|
|
746
|
+
streamer = null;
|
|
747
|
+
for (int i = 0; i < 3; i++) {
|
|
748
|
+
if (bitmapRingBuffer[i] != null) {
|
|
749
|
+
bitmapRingBuffer[i].recycle();
|
|
750
|
+
bitmapRingBuffer[i] = null;
|
|
751
|
+
}
|
|
637
752
|
}
|
|
638
|
-
|
|
753
|
+
latestBitmap = null;
|
|
754
|
+
scalePixelBuffer = null;
|
|
639
755
|
}
|
|
640
|
-
streamer = null;
|
|
641
|
-
latestBitmap = null;
|
|
642
756
|
Log.d(TAG, "Streaming stopped");
|
|
643
757
|
}
|
|
644
758
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ilabs-flir",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.12",
|
|
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",
|
|
@@ -67,4 +67,4 @@
|
|
|
67
67
|
"sourceDir": "./android/Flir"
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
-
}
|
|
70
|
+
}
|