Skip to content

Commit

Permalink
feat: Reintroduce Macros for Frame Processor Plugin registration (#2027)
Browse files Browse the repository at this point in the history
in VisionCamera v1 & v2 there were two ObjC macros that were helping
in creation/registration of Frame Processors, but these were removed with
v3

This PR reintroduces such macros, which will not only make FP development
easier, but also it will also fix issues people had with registration of
Swift Frame Processors (+load vs +initialize issues)

Docs were also updated to reflect that the macros should be used to
correctly initialize and register ObjC/Swift Frame Processors
  • Loading branch information
mateusz1913 authored Oct 19, 2023
1 parent 2666ac5 commit a291642
Show file tree
Hide file tree
Showing 15 changed files with 92 additions and 60 deletions.
10 changes: 6 additions & 4 deletions docs/docs/guides/FRAME_PROCESSOR_CREATE_PLUGIN_ANDROID.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ import com.mrousavy.camera.frameprocessor.Frame;
import com.mrousavy.camera.frameprocessor.FrameProcessorPlugin;

public class FaceDetectorFrameProcessorPlugin extends FrameProcessorPlugin {
FaceDetectorFrameProcessorPlugin(@Nullable Map<String, Object> options) {}

@Nullable
@Override
public Object callback(@NonNull Frame frame, @Nullable Map<String, Object> arguments) {
Expand All @@ -87,7 +89,7 @@ import com.mrousavy.camera.frameprocessor.FrameProcessorPluginRegistry;
public class FaceDetectorFrameProcessorPluginPackage implements ReactPackage {
// highlight-start
FaceDetectorFrameProcessorPluginPackage() {
FrameProcessorPluginRegistry.addFrameProcessorPlugin("detectFaces", options -> new FaceDetectorFrameProcessorPlugin());
FrameProcessorPluginRegistry.addFrameProcessorPlugin("detectFaces", options -> new FaceDetectorFrameProcessorPlugin(options));
}
// highlight-end

Expand Down Expand Up @@ -134,9 +136,9 @@ The Frame Processor Plugin will be exposed to JS through the `VisionCameraProxy`
import com.mrousavy.camera.frameprocessor.Frame
import com.mrousavy.camera.frameprocessor.FrameProcessorPlugin

class FaceDetectorFrameProcessorPlugin: FrameProcessorPlugin() {
class FaceDetectorFrameProcessorPlugin(options: Map<String, Any>?): FrameProcessorPlugin(options) {

override fun callback(frame: Frame, arguments: Map<String, Object>?): Any? {
override fun callback(frame: Frame, arguments: Map<String, Any>?): Any? {
// highlight-next-line
// code goes here
return null
Expand All @@ -158,7 +160,7 @@ class FaceDetectorFrameProcessorPluginPackage : ReactPackage {
// highlight-start
init {
FrameProcessorPluginRegistry.addFrameProcessorPlugin("detectFaces") { options ->
FaceDetectorFrameProcessorPlugin()
FaceDetectorFrameProcessorPlugin(options)
}
}
// highlight-end
Expand Down
36 changes: 11 additions & 25 deletions docs/docs/guides/FRAME_PROCESSOR_CREATE_PLUGIN_IOS.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ For reference see the [CLI's docs](https://github.com/mateusz1913/vision-camera-
@implementation FaceDetectorFrameProcessorPlugin

- (instancetype) initWithOptions:(NSDictionary*)options; {
self = [super init];
self = [super initWithOptions:options];
return self;
}

Expand All @@ -63,14 +63,9 @@ For reference see the [CLI's docs](https://github.com/mateusz1913/vision-camera-
return nil;
}

+ (void) load {
// highlight-start
[FrameProcessorPluginRegistry addFrameProcessorPlugin:@"detectFaces"
withInitializer:^FrameProcessorPlugin*(NSDictionary* options) {
return [[FaceDetectorFrameProcessorPlugin alloc] initWithOptions:options];
}];
// highlight-end
}
// highlight-start
VISION_EXPORT_FRAME_PROCESSOR(FaceDetectorFrameProcessorPlugin, detectFaces)
// highlight-end

@end
```
Expand All @@ -96,6 +91,10 @@ import VisionCamera
@objc(FaceDetectorFrameProcessorPlugin)
public class FaceDetectorFrameProcessorPlugin: FrameProcessorPlugin {
public override init(options: [AnyHashable : Any]! = [:]) {
super.init(options: options)
}
public override func callback(_ frame: Frame, withArguments arguments: [AnyHashable : Any]?) -> Any {
let buffer = frame.buffer
let orientation = frame.orientation
Expand All @@ -113,22 +112,9 @@ public class FaceDetectorFrameProcessorPlugin: FrameProcessorPlugin {

#import "YOUR_XCODE_PROJECT_NAME-Swift.h" // <--- replace "YOUR_XCODE_PROJECT_NAME" with the actual value of your xcode project name

@interface FaceDetectorFrameProcessorPlugin (FrameProcessorPluginLoader)
@end

@implementation FaceDetectorFrameProcessorPlugin (FrameProcessorPluginLoader)

+ (void)load
{
// highlight-start
[FrameProcessorPluginRegistry addFrameProcessorPlugin:@"detectFaces"
withInitializer:^FrameProcessorPlugin* (NSDictionary* options) {
return [[FaceDetectorFrameProcessorPlugin alloc] initWithOptions:options];
}];
// highlight-end
}

@end
// highlight-start
VISION_EXPORT_SWIFT_FRAME_PROCESSOR(FaceDetectorFrameProcessorPlugin, detectFaces)
// highlight-end
```
5. **Implement your frame processing.** See [Example Plugin (Swift)](https://github.com/mrousavy/react-native-vision-camera/blob/main/package/example/ios/Frame%20Processor%20Plugins/Example%20Plugin%20%28Swift%29) for reference.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@
@DoNotStrip
@Keep
public abstract class FrameProcessorPlugin {
public FrameProcessorPlugin() {}

/**
* The initializer for a Frame Processor Plugin class that takes optional object that consists
* options passed from JS layer
*/
public FrameProcessorPlugin(@Nullable Map<String, Object> options) {}

/**
* The actual Frame Processor plugin callback. Called for every frame the ImageAnalyzer receives.
* @param frame The Frame from the Camera. Don't call .close() on this, as VisionCamera handles that.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public Object callback(@NotNull Frame frame, @Nullable Map<String, Object> param
return map;
}

ExampleFrameProcessorPlugin() {

ExampleFrameProcessorPlugin(@Nullable Map<String, Object> options) {
Log.d("ExamplePlugin", " - options: " + options.toString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import android.util.Log
import com.mrousavy.camera.frameprocessor.Frame
import com.mrousavy.camera.frameprocessor.FrameProcessorPlugin

class ExampleKotlinFrameProcessorPlugin: FrameProcessorPlugin() {
class ExampleKotlinFrameProcessorPlugin(options: Map<String, Any>?): FrameProcessorPlugin(options) {
init {
Log.d("ExampleKotlinPlugin", " - options" + options?.toString())
}

override fun callback(frame: Frame, params: Map<String, Any>?): Any? {
if (params == null) {
return null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public void onCreate() {
DefaultNewArchitectureEntryPoint.load();
}

FrameProcessorPluginRegistry.addFrameProcessorPlugin("example_plugin", options -> new ExampleFrameProcessorPlugin());
FrameProcessorPluginRegistry.addFrameProcessorPlugin("example_kotlin_swift_plugin", options -> new ExampleKotlinFrameProcessorPlugin());
FrameProcessorPluginRegistry.addFrameProcessorPlugin("example_plugin", options -> new ExampleFrameProcessorPlugin(options));
FrameProcessorPluginRegistry.addFrameProcessorPlugin("example_kotlin_swift_plugin", options -> new ExampleKotlinFrameProcessorPlugin(options));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ @interface ExampleFrameProcessorPlugin : FrameProcessorPlugin

@implementation ExampleFrameProcessorPlugin

- (instancetype)initWithOptions:(NSDictionary * _Nullable)options
{
self = [super initWithOptions:options];
NSLog(@"ExamplePlugin - options: %@", options);
return self;
}

- (id)callback:(Frame *)frame withArguments:(NSDictionary *)arguments {
CVPixelBufferRef imageBuffer = CMSampleBufferGetImageBuffer(frame.buffer);
NSLog(@"ExamplePlugin: %zu x %zu Image. Logging %lu parameters:", CVPixelBufferGetWidth(imageBuffer), CVPixelBufferGetHeight(imageBuffer), (unsigned long)arguments.count);
Expand All @@ -38,12 +45,7 @@ - (id)callback:(Frame *)frame withArguments:(NSDictionary *)arguments {
};
}

+ (void) load {
[FrameProcessorPluginRegistry addFrameProcessorPlugin:@"example_plugin"
withInitializer:^FrameProcessorPlugin*(NSDictionary* options) {
return [[ExampleFrameProcessorPlugin alloc] init];
}];
}
VISION_EXPORT_FRAME_PROCESSOR(ExampleFrameProcessorPlugin, example_plugin)

@end
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,12 @@
//

#if __has_include(<VisionCamera/FrameProcessorPlugin.h>)
#import <Foundation/Foundation.h>
#import <VisionCamera/FrameProcessorPlugin.h>
#import <VisionCamera/FrameProcessorPluginRegistry.h>
#import <VisionCamera/Frame.h>

#import "VisionCameraExample-Swift.h"

// Example for a Swift Frame Processor plugin automatic registration
@interface ExampleSwiftFrameProcessorPlugin (FrameProcessorPluginLoader)
@end

@implementation ExampleSwiftFrameProcessorPlugin (FrameProcessorPluginLoader)

+ (void)initialize {
[FrameProcessorPluginRegistry addFrameProcessorPlugin:@"example_kotlin_swift_plugin"
withInitializer:^FrameProcessorPlugin* _Nonnull(NSDictionary* _Nullable options) {
return [[ExampleSwiftFrameProcessorPlugin alloc] init];
}];
}

@end
// // Example for a Swift Frame Processor plugin automatic registration
VISION_EXPORT_SWIFT_FRAME_PROCESSOR(ExampleSwiftFrameProcessorPlugin, example_kotlin_swift_plugin)

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ import VisionCamera
// Example for a Swift Frame Processor plugin
@objc(ExampleSwiftFrameProcessorPlugin)
public class ExampleSwiftFrameProcessorPlugin: FrameProcessorPlugin {
public override init(options: [AnyHashable: Any]! = [:]) {
super.init(options: options)

print("ExampleSwiftPlugin - options: \(String(describing: options))")
}

public override func callback(_ frame: Frame, withArguments arguments: [AnyHashable: Any]?) -> Any? {
let imageBuffer = CMSampleBufferGetImageBuffer(frame.buffer)

Expand Down
4 changes: 2 additions & 2 deletions package/example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ PODS:
- libwebp (~> 1.0)
- SDWebImage/Core (~> 5.10)
- SocketRocket (0.6.1)
- VisionCamera (3.3.1):
- VisionCamera (3.4.0):
- React
- React-callinvoker
- React-Core
Expand Down Expand Up @@ -747,7 +747,7 @@ SPEC CHECKSUMS:
SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d
SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
VisionCamera: f386aee60abb07d979c506ea9e6d4831e596cafe
VisionCamera: eead9df29ac5935d5685b5ecaea3ae8b6da84bff
Yoga: 8796b55dba14d7004f980b54bcc9833ee45b28ce

PODFILE CHECKSUM: 27f53791141a3303d814e09b55770336416ff4eb
Expand Down
2 changes: 2 additions & 0 deletions package/example/src/CameraPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type { Routes } from './Routes'
import type { NativeStackScreenProps } from '@react-navigation/native-stack'
import { useIsFocused } from '@react-navigation/core'
import { examplePlugin } from './frame-processors/ExamplePlugin'
import { exampleKotlinSwiftPlugin } from './frame-processors/ExampleKotlinSwiftPlugin'
import { usePreferredCameraDevice } from './hooks/usePreferredCameraDevice'

const ReanimatedCamera = Reanimated.createAnimatedComponent(Camera)
Expand Down Expand Up @@ -166,6 +167,7 @@ export function CameraPage({ navigation }: Props): React.ReactElement {

console.log(`${frame.timestamp}: ${frame.width}x${frame.height} ${frame.pixelFormat} Frame (${frame.orientation})`)
examplePlugin(frame)
exampleKotlinSwiftPlugin(frame)
}, [])

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { VisionCameraProxy, Frame } from 'react-native-vision-camera'

const plugin = VisionCameraProxy.getFrameProcessorPlugin('example_kotlin_swift_plugin')
const plugin = VisionCameraProxy.getFrameProcessorPlugin('example_kotlin_swift_plugin', { foo: 'bar' })

export function exampleKotlinSwiftPlugin(frame: Frame): string[] {
'worklet'
Expand Down
31 changes: 31 additions & 0 deletions package/ios/Frame Processor/FrameProcessorPlugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,39 @@
/// VisionCamera Runtime.
@interface FrameProcessorPlugin : NSObject

/// The initializer for a Frame Processor Plugin class that takes optional object that consists
/// options passed from JS layer
- (instancetype _Nonnull)initWithOptions:(NSDictionary* _Nullable)options;

/// The actual callback when calling this plugin. Any Frame Processing should be handled there.
/// Make sure your code is optimized, as this is a hot path.
- (id _Nullable)callback:(Frame* _Nonnull)frame withArguments:(NSDictionary* _Nullable)arguments;

@end

#define VISION_CONCAT2(A, B) A##B
#define VISION_CONCAT(A, B) VISION_CONCAT2(A, B)

#define VISION_EXPORT_FRAME_PROCESSOR(frame_processor_class, frame_processor_plugin_name) \
+(void)load { \
[FrameProcessorPluginRegistry addFrameProcessorPlugin:@ #frame_processor_plugin_name \
withInitializer:^FrameProcessorPlugin*(NSDictionary* _Nullable options) { \
return [[frame_processor_class alloc] initWithOptions:options]; \
}]; \
}

#define VISION_EXPORT_SWIFT_FRAME_PROCESSOR(frame_processor_class, frame_processor_plugin_name) \
\
@interface frame_processor_class (FrameProcessorPluginLoader) \
@end \
\
@implementation frame_processor_class (FrameProcessorPluginLoader) \
\
__attribute__((constructor)) static void VISION_CONCAT(initialize_, frame_processor_plugin_name)(void) { \
[FrameProcessorPluginRegistry addFrameProcessorPlugin:@ #frame_processor_plugin_name \
withInitializer:^FrameProcessorPlugin* _Nonnull(NSDictionary* _Nullable options) { \
return [[frame_processor_class alloc] initWithOptions:options]; \
}]; \
} \
\
@end
5 changes: 5 additions & 0 deletions package/ios/Frame Processor/FrameProcessorPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
// Base implementation (empty)
@implementation FrameProcessorPlugin

- (instancetype)initWithOptions:(NSDictionary* _Nullable)options {
self = [super init];
return self;
}

- (id _Nullable)callback:(Frame* _Nonnull)frame withArguments:(NSDictionary* _Nullable)arguments {
[NSException raise:NSInternalInconsistencyException
format:@"Frame Processor Plugin does not override the `callback(frame:withArguments:)` method!"];
Expand Down
2 changes: 1 addition & 1 deletion package/src/FrameProcessorPlugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ interface TVisionCameraProxy {
* Creates a new instance of a Frame Processor Plugin.
* The Plugin has to be registered on the native side, otherwise this returns `undefined`
*/
getFrameProcessorPlugin: (name: string) => FrameProcessorPlugin | undefined
getFrameProcessorPlugin: (name: string, options?: Record<string, ParameterType>) => FrameProcessorPlugin | undefined
}

let hasWorklets = false
Expand Down

1 comment on commit a291642

@vercel
Copy link

@vercel vercel bot commented on a291642 Oct 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.