WIP: add portrait FxPlug controls, render adapter and face selection OSC
这个提交包含在:
1 父节点
f71826b64f
当前提交
e2803e727f
12 个文件已更改
+476
-814
没有与搜索条件相匹配的文件
@@ -252,7 +252,7 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = (
|
||||
"mkdir -p \"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}\"",
|
||||
"rsync --archive --links --whole-file --no-owner --no-group --checksum --exclude='Headers' \"/Library/Developer/Frameworks/FxPlug.framework/\" \"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FxPlug.framework/\"",
|
||||
"rsync --archive --links --whole-file --no-owner --no-group --checksum --exclude='Headers' \"/Library/Developer/SDKs/FxPlug.sdk/Library/Frameworks/FxPlug.framework/\" \"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FxPlug.framework/\"",
|
||||
"if [ \"$?\" -ne 0 ]; then echo \"error: rsync failed; couldn't embed FxPlug.framework\"; exit 1; fi",
|
||||
"codesign --force --timestamp --options runtime --sign \"${CODE_SIGN_IDENTITY}\" \"${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/FxPlug.framework\"",
|
||||
);
|
||||
@@ -264,7 +264,7 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = (
|
||||
"mkdir -p \"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}\"",
|
||||
"rsync --archive --links --whole-file --no-owner --no-group --checksum --exclude='Headers' \"/Library/Developer/Frameworks/PluginManager.framework/\" \"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PluginManager.framework/\"",
|
||||
"rsync --archive --links --whole-file --no-owner --no-group --checksum --exclude='Headers' \"/Library/Developer/SDKs/FxPlug.sdk/Library/Frameworks/PluginManager.framework/\" \"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PluginManager.framework/\"",
|
||||
"if [ \"$?\" -ne 0 ]; then echo \"error: rsync failed; couldn't embed PluginManager.framework\"; exit 1; fi",
|
||||
"codesign --force --timestamp --options runtime --sign \"${CODE_SIGN_IDENTITY}\" \"${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/PluginManager.framework\"",
|
||||
"",
|
||||
@@ -303,7 +303,9 @@
|
||||
6945FCCD3088B7220013E108 /* Debug configuration for PBXNativeTarget "XPC Service" */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CODE_SIGN_IDENTITY = "Developer ID Application";
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
DEVELOPMENT_TEAM = B3W25FX5H5;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
@@ -313,7 +315,7 @@
|
||||
"@loader_path/../Frameworks",
|
||||
);
|
||||
MACH_O_TYPE = mh_execute;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.dayu.beatygril.fxplug;
|
||||
PRODUCT_NAME = "beatygril $(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "beatygril/Plugin/XPC Service-Bridging-Header.h";
|
||||
WRAPPER_EXTENSION = pluginkit;
|
||||
@@ -323,7 +325,9 @@
|
||||
6945FCCE3088B7220013E108 /* Release configuration for PBXNativeTarget "XPC Service" */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CODE_SIGN_IDENTITY = "Developer ID Application";
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
DEVELOPMENT_TEAM = B3W25FX5H5;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
@@ -333,7 +337,7 @@
|
||||
"@loader_path/../Frameworks",
|
||||
);
|
||||
MACH_O_TYPE = mh_execute;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.dayu.beatygril.fxplug;
|
||||
PRODUCT_NAME = "beatygril $(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "beatygril/Plugin/XPC Service-Bridging-Header.h";
|
||||
WRAPPER_EXTENSION = pluginkit;
|
||||
@@ -344,12 +348,13 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = "beatygril/Wrapper Application/SandboxEntitlements.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = B3W25FX5H5;
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CODE_SIGN_IDENTITY = "Developer ID Application";
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
DEVELOPMENT_TEAM = B3W25FX5H5;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
INFOPLIST_FILE = "beatygril/Wrapper Application/Info.plist";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.dayu.beatygril;
|
||||
PRODUCT_NAME = beatygril;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "beatygril/Wrapper Application/Wrapper Application-Bridging-Header.h";
|
||||
};
|
||||
@@ -359,12 +364,13 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = "beatygril/Wrapper Application/SandboxEntitlements.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = B3W25FX5H5;
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CODE_SIGN_IDENTITY = "Developer ID Application";
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
DEVELOPMENT_TEAM = B3W25FX5H5;
|
||||
GCC_PRECOMPILE_PREFIX_HEADER = YES;
|
||||
INFOPLIST_FILE = "beatygril/Wrapper Application/Info.plist";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.dayu.beatygril;
|
||||
PRODUCT_NAME = beatygril;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "beatygril/Wrapper Application/Wrapper Application-Bridging-Header.h";
|
||||
};
|
||||
@@ -380,7 +386,7 @@
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = NO;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
@@ -411,7 +417,7 @@
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
/Library/Frameworks,
|
||||
/Library/Developer/SDKs/FxPlug.sdk/Library/Frameworks,
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
@@ -430,7 +436,7 @@
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 27.2;
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
@@ -449,7 +455,7 @@
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = NO;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
@@ -480,7 +486,7 @@
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
/Library/Frameworks,
|
||||
/Library/Developer/SDKs/FxPlug.sdk/Library/Frameworks,
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
@@ -493,7 +499,7 @@
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 27.2;
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = macosx;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>beatygrilXPCService</string>
|
||||
<string>大鱼人像美化</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
@@ -48,7 +48,7 @@
|
||||
<array>
|
||||
<dict>
|
||||
<key>groupName</key>
|
||||
<string>beatygril Group</string>
|
||||
<string>大鱼</string>
|
||||
<key>uuid</key>
|
||||
<string>0461C6B0-B8B7-4DE8-B882-03C635553ECB</string>
|
||||
</dict>
|
||||
@@ -59,20 +59,41 @@
|
||||
<key>className</key>
|
||||
<string>beatygrilPlugIn</string>
|
||||
<key>displayName</key>
|
||||
<string>beatygril::FxPlug Name</string>
|
||||
<string>大鱼人像美化</string>
|
||||
<key>group</key>
|
||||
<string>0461C6B0-B8B7-4DE8-B882-03C635553ECB</string>
|
||||
<key>infoString</key>
|
||||
<string>beatygril::FxPlug Description</string>
|
||||
<string>人像磨皮、肤色、脸型和人脸跟踪</string>
|
||||
<key>protocolNames</key>
|
||||
<array>
|
||||
<string>FxFilter</string>
|
||||
<string>FxAnalyzer</string>
|
||||
</array>
|
||||
<key>uuid</key>
|
||||
<string>FB14B0E6-59B2-4836-916F-CD0C559E9241</string>
|
||||
<key>version</key>
|
||||
<string>1.0</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>className</key>
|
||||
<string>PGFaceSelectOSC</string>
|
||||
<key>displayName</key>
|
||||
<string>人脸选择</string>
|
||||
<key>group</key>
|
||||
<string>0461C6B0-B8B7-4DE8-B882-03C635553ECB</string>
|
||||
<key>uuid</key>
|
||||
<string>E0E432C8-3D71-4ABA-B006-E7F519E06C87</string>
|
||||
<key>version</key>
|
||||
<string>1.0</string>
|
||||
<key>protocolNames</key>
|
||||
<array>
|
||||
<string>FxOnScreenControl</string>
|
||||
</array>
|
||||
<key>supportedPlugins</key>
|
||||
<array>
|
||||
<string>FB14B0E6-59B2-4836-916F-CD0C559E9241</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>XPCService</key>
|
||||
<dict>
|
||||
@@ -88,5 +109,7 @@
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>12.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,28 +0,0 @@
|
||||
//
|
||||
// MetalDeviceCache.h
|
||||
// beatygril
|
||||
//
|
||||
// Created by dayu on 2026/10/21.
|
||||
//
|
||||
|
||||
#import <Metal/Metal.h>
|
||||
#import <FxPlug/FxPlugSDK.h>
|
||||
|
||||
@class MetalDeviceCacheItem;
|
||||
|
||||
@interface MetalDeviceCache : NSObject
|
||||
{
|
||||
NSMutableArray<MetalDeviceCacheItem*>* deviceCaches;
|
||||
}
|
||||
|
||||
+ (MetalDeviceCache*)deviceCache;
|
||||
+ (MTLPixelFormat)MTLPixelFormatForImageTile:(FxImageTile*)imageTile;
|
||||
|
||||
- (id<MTLDevice>)deviceWithRegistryID:(uint64_t)registryID;
|
||||
- (id<MTLRenderPipelineState>)pipelineStateWithRegistryID:(uint64_t)registryID
|
||||
pixelFormat:(MTLPixelFormat)pixFormat;
|
||||
- (id<MTLCommandQueue>)commandQueueWithRegistryID:(uint64_t)registryID
|
||||
pixelFormat:(MTLPixelFormat)pixFormat;
|
||||
- (void)returnCommandQueueToCache:(id<MTLCommandQueue>)commandQueue;
|
||||
|
||||
@end
|
||||
@@ -1,339 +0,0 @@
|
||||
//
|
||||
// MetalDeviceCache.m
|
||||
// beatygril
|
||||
//
|
||||
// Created by dayu on 2026/10/21.
|
||||
//
|
||||
|
||||
#import "MetalDeviceCache.h"
|
||||
|
||||
const NSUInteger kMaxCommandQueues = 5;
|
||||
static NSString* kKey_InUse = @"InUse";
|
||||
static NSString* kKey_CommandQueue = @"CommandQueue";
|
||||
|
||||
static MetalDeviceCache* gDeviceCache = nil;
|
||||
|
||||
@interface MetalDeviceCacheItem : NSObject
|
||||
|
||||
@property (readonly) id<MTLDevice> gpuDevice;
|
||||
@property (readonly) id<MTLRenderPipelineState> pipelineState;
|
||||
@property (retain) NSMutableArray<NSMutableDictionary*>* commandQueueCache;
|
||||
@property (readonly) NSLock* commandQueueCacheLock;
|
||||
@property (readonly) MTLPixelFormat pixelFormat;
|
||||
|
||||
- (instancetype)initWithDevice:(id<MTLDevice>)device
|
||||
pixelFormat:(MTLPixelFormat)pixFormat;
|
||||
- (id<MTLCommandQueue>)getNextFreeCommandQueue;
|
||||
- (void)returnCommandQueue:(id<MTLCommandQueue>)commandQueue;
|
||||
- (BOOL)containsCommandQueue:(id<MTLCommandQueue>)commandQueue;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MetalDeviceCacheItem
|
||||
|
||||
- (instancetype)initWithDevice:(id<MTLDevice>)device
|
||||
pixelFormat:(MTLPixelFormat)pixFormat;
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self != nil)
|
||||
{
|
||||
_gpuDevice = [device retain];
|
||||
|
||||
_commandQueueCache = [[NSMutableArray alloc] initWithCapacity:kMaxCommandQueues];
|
||||
for (NSUInteger i = 0; (_commandQueueCache != nil) && (i < kMaxCommandQueues); i++)
|
||||
{
|
||||
NSMutableDictionary* commandDict = [NSMutableDictionary dictionary];
|
||||
[commandDict setObject:[NSNumber numberWithBool:NO]
|
||||
forKey:kKey_InUse];
|
||||
|
||||
id<MTLCommandQueue> commandQueue = [_gpuDevice newCommandQueue];
|
||||
[commandDict setObject:commandQueue
|
||||
forKey:kKey_CommandQueue];
|
||||
|
||||
[_commandQueueCache addObject:commandDict];
|
||||
}
|
||||
|
||||
// Load all the shader files with a .metal file extension in the project
|
||||
id<MTLLibrary> defaultLibrary = [[_gpuDevice newDefaultLibrary] autorelease];
|
||||
|
||||
// Load the vertex function from the library
|
||||
id<MTLFunction> vertexFunction = [[defaultLibrary newFunctionWithName:@"vertexShader"] autorelease];
|
||||
|
||||
// Load the fragment function from the library
|
||||
id<MTLFunction> fragmentFunction = [[defaultLibrary newFunctionWithName:@"fragmentShader"] autorelease];
|
||||
|
||||
// Configure a pipeline descriptor that is used to create a pipeline state
|
||||
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[[MTLRenderPipelineDescriptor alloc] init] autorelease];
|
||||
pipelineStateDescriptor.label = @"Simple Pipeline";
|
||||
pipelineStateDescriptor.vertexFunction = vertexFunction;
|
||||
pipelineStateDescriptor.fragmentFunction = fragmentFunction;
|
||||
pipelineStateDescriptor.colorAttachments[0].pixelFormat = pixFormat;
|
||||
_pixelFormat = pixFormat;
|
||||
|
||||
NSError* error = nil;
|
||||
_pipelineState = [_gpuDevice newRenderPipelineStateWithDescriptor:pipelineStateDescriptor
|
||||
error:&error];
|
||||
if (error != nil)
|
||||
{
|
||||
NSLog (@"Error generating brightness pipeline state: %@", error);
|
||||
}
|
||||
|
||||
if (_commandQueueCache != nil)
|
||||
{
|
||||
_commandQueueCacheLock = [[NSLock alloc] init];
|
||||
}
|
||||
|
||||
if ((_gpuDevice == nil) || (_commandQueueCache == nil) || (_commandQueueCacheLock == nil) ||
|
||||
(_pipelineState == nil))
|
||||
{
|
||||
[self release];
|
||||
self = nil;
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[_gpuDevice release];
|
||||
[_commandQueueCache release];
|
||||
[_commandQueueCacheLock release];
|
||||
[_pipelineState release];
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (id<MTLCommandQueue>)getNextFreeCommandQueue
|
||||
{
|
||||
id<MTLCommandQueue> result = nil;
|
||||
|
||||
[_commandQueueCacheLock lock];
|
||||
NSUInteger index = 0;
|
||||
while ((result == nil) && (index < kMaxCommandQueues))
|
||||
{
|
||||
NSMutableDictionary* nextCommandQueue = [_commandQueueCache objectAtIndex:index];
|
||||
NSNumber* inUse = [nextCommandQueue objectForKey:kKey_InUse];
|
||||
if (![inUse boolValue])
|
||||
{
|
||||
[nextCommandQueue setObject:[NSNumber numberWithBool:YES]
|
||||
forKey:kKey_InUse];
|
||||
result = [nextCommandQueue objectForKey:kKey_CommandQueue];
|
||||
}
|
||||
index++;
|
||||
}
|
||||
[_commandQueueCacheLock unlock];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)returnCommandQueue:(id<MTLCommandQueue>)commandQueue
|
||||
{
|
||||
[_commandQueueCacheLock lock];
|
||||
|
||||
BOOL found = false;
|
||||
NSUInteger index = 0;
|
||||
while ((!found) && (index < kMaxCommandQueues))
|
||||
{
|
||||
NSMutableDictionary* nextCommandQueuDict = [_commandQueueCache objectAtIndex:index];
|
||||
id<MTLCommandQueue> nextCommandQueue = [nextCommandQueuDict objectForKey:kKey_CommandQueue];
|
||||
if (nextCommandQueue == commandQueue)
|
||||
{
|
||||
found = YES;
|
||||
[nextCommandQueuDict setObject:[NSNumber numberWithBool:NO]
|
||||
forKey:kKey_InUse];
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
[_commandQueueCacheLock unlock];
|
||||
}
|
||||
|
||||
- (BOOL)containsCommandQueue:(id<MTLCommandQueue>)commandQueue
|
||||
{
|
||||
BOOL found = NO;
|
||||
NSUInteger index = 0;
|
||||
while ((!found) && (index < kMaxCommandQueues))
|
||||
{
|
||||
NSMutableDictionary* nextCommandQueuDict = [_commandQueueCache objectAtIndex:index];
|
||||
id<MTLCommandQueue> nextCommandQueue = [nextCommandQueuDict objectForKey:kKey_CommandQueue];
|
||||
if (nextCommandQueue == commandQueue)
|
||||
{
|
||||
found = YES;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MetalDeviceCache
|
||||
|
||||
+ (MTLPixelFormat)MTLPixelFormatForImageTile:(FxImageTile*)imageTile
|
||||
{
|
||||
MTLPixelFormat result = MTLPixelFormatRGBA16Float;
|
||||
|
||||
switch (imageTile.ioSurface.pixelFormat)
|
||||
{
|
||||
case kCVPixelFormatType_128RGBAFloat:
|
||||
result = MTLPixelFormatRGBA32Float;
|
||||
break;
|
||||
|
||||
case kCVPixelFormatType_32BGRA:
|
||||
result = MTLPixelFormatBGRA8Unorm;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
+ (MetalDeviceCache*)deviceCache;
|
||||
{
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
gDeviceCache = [[MetalDeviceCache alloc] init];
|
||||
});
|
||||
|
||||
return gDeviceCache;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self != nil)
|
||||
{
|
||||
NSArray<id<MTLDevice>>* devices = MTLCopyAllDevices();
|
||||
|
||||
deviceCaches = [[NSMutableArray alloc] initWithCapacity:devices.count];
|
||||
|
||||
for (id<MTLDevice> nextDevice in devices)
|
||||
{
|
||||
MetalDeviceCacheItem* newCacheItem = [[[MetalDeviceCacheItem alloc] initWithDevice:nextDevice
|
||||
pixelFormat:MTLPixelFormatRGBA16Float]
|
||||
autorelease];
|
||||
[deviceCaches addObject:newCacheItem];
|
||||
}
|
||||
|
||||
[devices release];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[deviceCaches release];
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (id<MTLDevice>)deviceWithRegistryID:(uint64_t)registryID
|
||||
{
|
||||
for (MetalDeviceCacheItem* nextCacheItem in deviceCaches)
|
||||
{
|
||||
if (nextCacheItem.gpuDevice.registryID == registryID)
|
||||
{
|
||||
return nextCacheItem.gpuDevice;
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (id<MTLRenderPipelineState>)pipelineStateWithRegistryID:(uint64_t)registryID
|
||||
pixelFormat:(MTLPixelFormat)pixFormat
|
||||
{
|
||||
for (MetalDeviceCacheItem* nextCacheItem in deviceCaches)
|
||||
{
|
||||
if ((nextCacheItem.gpuDevice.registryID == registryID) &&
|
||||
(nextCacheItem.pixelFormat == pixFormat))
|
||||
{
|
||||
return nextCacheItem.pipelineState;
|
||||
}
|
||||
}
|
||||
// Didn't find one, so create one with the right settings
|
||||
NSArray<id<MTLDevice>>* devices = MTLCopyAllDevices();
|
||||
id<MTLDevice> device = nil;
|
||||
for (id<MTLDevice> nextDevice in devices)
|
||||
{
|
||||
if (nextDevice.registryID == registryID)
|
||||
{
|
||||
device = nextDevice;
|
||||
}
|
||||
}
|
||||
|
||||
id<MTLRenderPipelineState> result = nil;
|
||||
if (device != nil)
|
||||
{
|
||||
MetalDeviceCacheItem* newCacheItem = [[[MetalDeviceCacheItem alloc] initWithDevice:device
|
||||
pixelFormat:pixFormat]
|
||||
autorelease];
|
||||
if (newCacheItem != nil)
|
||||
{
|
||||
[deviceCaches addObject:newCacheItem];
|
||||
result = newCacheItem.pipelineState;
|
||||
}
|
||||
}
|
||||
[devices release];
|
||||
return result;
|
||||
}
|
||||
|
||||
- (id<MTLCommandQueue>)commandQueueWithRegistryID:(uint64_t)registryID
|
||||
pixelFormat:(MTLPixelFormat)pixFormat;
|
||||
{
|
||||
for (MetalDeviceCacheItem* nextCacheItem in deviceCaches)
|
||||
{
|
||||
if ((nextCacheItem.gpuDevice.registryID == registryID) &&
|
||||
(nextCacheItem.pixelFormat == pixFormat))
|
||||
{
|
||||
return [nextCacheItem getNextFreeCommandQueue];
|
||||
}
|
||||
}
|
||||
|
||||
// Didn't find one, so create one with the right settings
|
||||
NSArray<id<MTLDevice>>* devices = MTLCopyAllDevices();
|
||||
id<MTLDevice> device = nil;
|
||||
for (id<MTLDevice> nextDevice in devices)
|
||||
{
|
||||
if (nextDevice.registryID == registryID)
|
||||
{
|
||||
device = nextDevice;
|
||||
}
|
||||
}
|
||||
|
||||
id<MTLCommandQueue> result = nil;
|
||||
if (device != nil)
|
||||
{
|
||||
MetalDeviceCacheItem* newCacheItem = [[[MetalDeviceCacheItem alloc] initWithDevice:device
|
||||
pixelFormat:pixFormat]
|
||||
autorelease];
|
||||
if (newCacheItem != nil)
|
||||
{
|
||||
[deviceCaches addObject:newCacheItem];
|
||||
result = [newCacheItem getNextFreeCommandQueue];
|
||||
}
|
||||
}
|
||||
[devices release];
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)returnCommandQueueToCache:(id<MTLCommandQueue>)commandQueue;
|
||||
{
|
||||
for (MetalDeviceCacheItem* nextCacheItem in deviceCaches)
|
||||
{
|
||||
if ([nextCacheItem containsCommandQueue:commandQueue])
|
||||
{
|
||||
[nextCacheItem returnCommandQueue:commandQueue];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
// Persisted FxPlug render state. Append fields and bump version for migrations.
|
||||
typedef struct {
|
||||
uint32_t version;
|
||||
float amount, skinBrightness, skinHue, skinSaturation, skinEvenTone;
|
||||
float midtoneDetail, highPassDetail, teethWhitening, eyeBrightening;
|
||||
float radius, sharpness, mix, maskThreshold, maskSoftness, maskEdgeProtect;
|
||||
float faceThin, faceBigEye, faceShort, faceNarrow, faceChin, faceV;
|
||||
float faceJaw, faceCheekbone, faceSmallHead, faceSmallHeadRange;
|
||||
float faceJumpGuardRange, selectionX, selectionY;
|
||||
uint32_t featureProtection, skinTonePreset, showMask, invertMask, debugChannel;
|
||||
uint32_t showFaceBoxes, showTrackBox, singleFace, fastMultiFace, trackingEnabled, targetIndex;
|
||||
} PGBeautySettings;
|
||||
|
||||
static inline PGBeautySettings PGBeautyDefaults(void) {
|
||||
PGBeautySettings s = {0};
|
||||
s.version = 1;
|
||||
s.amount = 1.0f;
|
||||
s.radius = 8.0f;
|
||||
s.mix = 1.0f;
|
||||
s.maskThreshold = 148.0f;
|
||||
s.maskSoftness = 0.6f;
|
||||
s.maskEdgeProtect = 0.4f;
|
||||
s.featureProtection = 1;
|
||||
s.faceJumpGuardRange = 1.8f;
|
||||
s.selectionX = s.selectionY = 0.5f;
|
||||
return s;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
#import <FxPlug/FxPlugSDK.h>
|
||||
#import <CoreImage/CoreImage.h>
|
||||
#import <IOSurface/IOSurface.h>
|
||||
#import "PGParameterIDs.h"
|
||||
|
||||
@interface PGFaceSelectOSC : NSObject <FxOnScreenControl_v4>
|
||||
@property (assign) id<PROAPIAccessing> apiManager;
|
||||
@end
|
||||
@implementation PGFaceSelectOSC
|
||||
- (instancetype)initWithAPIManager:(id<PROAPIAccessing>)api {
|
||||
if((self=[super init])) _apiManager=api; return self;
|
||||
}
|
||||
- (FxDrawingCoordinates)drawingCoordinates {return kFxDrawingCoordinates_OBJECT;}
|
||||
- (id<FxOnScreenControlAPI_v4>)oscAPI {return [_apiManager apiForProtocol:@protocol(FxOnScreenControlAPI_v4)];}
|
||||
- (BOOL)selectionEnabledAtTime:(CMTime)time {
|
||||
BOOL selected=NO;id<FxParameterRetrievalAPI_v6> api=[_apiManager apiForProtocol:@protocol(FxParameterRetrievalAPI_v6)];
|
||||
[api getBoolValue:&selected fromParameter:PGSingleFace atTime:time];return selected;
|
||||
}
|
||||
- (void)drawOSCWithWidth:(NSInteger)width height:(NSInteger)height activePart:(NSInteger)part destinationImage:(FxImageTile *)tile atTime:(CMTime)time {
|
||||
if(!tile.ioSurface||width<=0||height<=0) return;
|
||||
CIImage *output=[[CIImage imageWithColor:CIColor.clearColor] imageByCroppingToRect:CGRectMake(0,0,width,height)];
|
||||
if([self selectionEnabledAtTime:time]) {
|
||||
double x=.5,y=.5;
|
||||
id<FxParameterRetrievalAPI_v6> params=[_apiManager apiForProtocol:@protocol(FxParameterRetrievalAPI_v6)];
|
||||
[params getXValue:&x YValue:&y fromParameter:PGSelection atTime:time];
|
||||
CGRect bounds=[[self oscAPI] objectBounds];double canvasX=width*.5,canvasY=height*.5;
|
||||
[[self oscAPI] convertPointFromSpace:kFxDrawingCoordinates_OBJECT fromX:bounds.origin.x+x*bounds.size.width fromY:bounds.origin.y+y*bounds.size.height toSpace:kFxDrawingCoordinates_CANVAS toX:&canvasX toY:&canvasY];
|
||||
CGFloat scale=MAX(1,[[self oscAPI] backingScaleFactor]);
|
||||
size_t surfaceW=IOSurfaceGetWidth((__bridge IOSurfaceRef)tile.ioSurface),surfaceH=IOSurfaceGetHeight((__bridge IOSurfaceRef)tile.ioSurface);
|
||||
CGFloat sx=(CGFloat)surfaceW/width,sy=(CGFloat)surfaceH/height;
|
||||
canvasX*=sx;canvasY*=sy;CGFloat radius=10*scale;
|
||||
CIColor *color=part?[CIColor colorWithRed:1 green:.8 blue:.2]:[CIColor colorWithRed:.1 green:.9 blue:.8];
|
||||
CIImage *ring=[CIFilter filterWithName:@"CIRadialGradient" withInputParameters:@{@"inputCenter":[CIVector vectorWithX:canvasX Y:canvasY],@"inputRadius0":@(radius-2),@"inputRadius1":@(radius),@"inputColor0":color,@"inputColor1":CIColor.clearColor}].outputImage;
|
||||
CIImage *hole=[CIFilter filterWithName:@"CIRadialGradient" withInputParameters:@{@"inputCenter":[CIVector vectorWithX:canvasX Y:canvasY],@"inputRadius0":@(radius-3),@"inputRadius1":@(radius-2),@"inputColor0":CIColor.blackColor,@"inputColor1":CIColor.whiteColor}].outputImage;
|
||||
ring=[ring imageByApplyingFilter:@"CIBlendWithMask" withInputParameters:@{kCIInputBackgroundImageKey:output,kCIInputMaskImageKey:hole}];
|
||||
CIImage *v=[[CIImage imageWithColor:color] imageByCroppingToRect:CGRectMake(canvasX-scale,canvasY-16*scale,2*scale,32*scale)];
|
||||
CIImage *h=[[CIImage imageWithColor:color] imageByCroppingToRect:CGRectMake(canvasX-16*scale,canvasY-scale,32*scale,2*scale)];
|
||||
output=[[h imageByCompositingOverImage:v] imageByCompositingOverImage:ring];
|
||||
}
|
||||
CIContext *context=[CIContext contextWithOptions:@{kCIContextCacheIntermediates:@NO}];
|
||||
size_t w=IOSurfaceGetWidth((__bridge IOSurfaceRef)tile.ioSurface),h=IOSurfaceGetHeight((__bridge IOSurfaceRef)tile.ioSurface);
|
||||
if(tile.imageOrigin==kFxImageOrigin_TOP_LEFT) output=[output imageByApplyingTransform:CGAffineTransformMake(1,0,0,-1,0,h)];
|
||||
[context render:output toIOSurface:(__bridge IOSurfaceRef)tile.ioSurface bounds:CGRectMake(0,0,w,h) colorSpace:tile.colorSpace];
|
||||
}
|
||||
- (void)hitTestOSCAtMousePositionX:(double)x mousePositionY:(double)y activePart:(NSInteger *)part atTime:(CMTime)time {
|
||||
if(part) *part=([self selectionEnabledAtTime:time]&&CGRectContainsPoint([[self oscAPI] objectBounds],CGPointMake(x,y)))?1:0;
|
||||
}
|
||||
- (void)setSelectionX:(double)x y:(double)y time:(CMTime)time update:(BOOL *)update {
|
||||
CGRect bounds=[[self oscAPI] objectBounds];if(bounds.size.width<=0||bounds.size.height<=0) return;
|
||||
id<FxParameterSettingAPI_v5> api=[_apiManager apiForProtocol:@protocol(FxParameterSettingAPI_v5)];
|
||||
BOOL ok=[api setXValue:MIN(1,MAX(0,(x-bounds.origin.x)/bounds.size.width)) YValue:MIN(1,MAX(0,(y-bounds.origin.y)/bounds.size.height)) toParameter:PGSelection atTime:time];
|
||||
if(update) *update=ok;
|
||||
}
|
||||
- (void)mouseDownAtPositionX:(double)x positionY:(double)y activePart:(NSInteger)part modifiers:(FxModifierKeys)keys forceUpdate:(BOOL *)update atTime:(CMTime)time {
|
||||
id<FxUndoAPI> undo=[_apiManager apiForProtocol:@protocol(FxUndoAPI)];[undo startUndoGroup:@"选择人脸"];
|
||||
[self setSelectionX:x y:y time:time update:update];
|
||||
}
|
||||
- (void)mouseDraggedAtPositionX:(double)x positionY:(double)y activePart:(NSInteger)part modifiers:(FxModifierKeys)keys forceUpdate:(BOOL *)update atTime:(CMTime)time {[self setSelectionX:x y:y time:time update:update];}
|
||||
- (void)mouseUpAtPositionX:(double)x positionY:(double)y activePart:(NSInteger)part modifiers:(FxModifierKeys)keys forceUpdate:(BOOL *)update atTime:(CMTime)time {
|
||||
[self setSelectionX:x y:y time:time update:update];id<FxUndoAPI> undo=[_apiManager apiForProtocol:@protocol(FxUndoAPI)];[undo endUndoGroup];
|
||||
}
|
||||
- (void)keyDownAtPositionX:(double)x positionY:(double)y keyPressed:(unsigned short)key modifiers:(FxModifierKeys)modifiers forceUpdate:(BOOL *)update didHandle:(BOOL *)handled atTime:(CMTime)time {if(handled)*handled=NO;if(update)*update=NO;}
|
||||
- (void)keyUpAtPositionX:(double)x positionY:(double)y keyPressed:(unsigned short)key modifiers:(FxModifierKeys)modifiers forceUpdate:(BOOL *)update didHandle:(BOOL *)handled atTime:(CMTime)time {if(handled)*handled=NO;if(update)*update=NO;}
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
// Existing brightness sample ID 1 is migrated to the portrait amount control.
|
||||
enum {
|
||||
PGAmount=1, PGBrightness=2, PGRadius=3, PGSharpness=4, PGMix=5,
|
||||
PGThreshold=7, PGSoftness=8, PGEdgeProtect=9, PGShowMask=10,
|
||||
PGMidtone=12, PGHue=14, PGSaturation=15, PGHighPass=17, PGThin=18,
|
||||
PGFaceBoxes=19, PGSingleFace=20, PGSelection=21, PGShort=22,
|
||||
PGNarrow=23, PGChin=24, PGVFace=25, PGJaw=26, PGCheekbone=27,
|
||||
PGSmallHead=28, PGSmallHeadRange=29, PGFeatureProtect=30, PGTeeth=31,
|
||||
PGEyeBright=32, PGBigEye=33, PGTrackBoxes=35, PGFastMulti=39,
|
||||
PGJumpGuard=40, PGEvenTone=41, PGTonePreset=42, PGInvertMask=43,
|
||||
PGDebugChannel=44, PGTrackEnabled=45, PGTarget=46,
|
||||
PGAnalyzeForward=50, PGAnalyzeBackward=51, PGClearTrack=52, PGStatus=53,
|
||||
PGTrackData1=60, PGTrackData2=61, PGTrackData3=62,
|
||||
PGSmoothingEnabled=70, PGColorEnabled=71, PGShapeEnabled=72,
|
||||
PGDetailEnabled=73, PGHelp=74
|
||||
};
|
||||
@@ -1,83 +0,0 @@
|
||||
//
|
||||
// TileableRemoteBrightness.metal
|
||||
// beatygril
|
||||
//
|
||||
// Created by dayu on 2026/10/21.
|
||||
//
|
||||
|
||||
#include <metal_stdlib>
|
||||
#include <simd/simd.h>
|
||||
|
||||
using namespace metal;
|
||||
|
||||
#include "TileableRemoteBrightnessShaderTypes.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
// The [[position]] attribute of this member indicates that this value is the clip space
|
||||
// position of the vertex when this structure is returned from the vertex function
|
||||
float4 clipSpacePosition [[position]];
|
||||
|
||||
// Since this member does not have a special attribute, the rasterizer interpolates
|
||||
// its value with the values of the other triangle vertices and then passes
|
||||
// the interpolated value to the fragment shader for each fragment in the triangle
|
||||
float2 textureCoordinate;
|
||||
|
||||
} RasterizerData;
|
||||
|
||||
vertex RasterizerData
|
||||
vertexShader(uint vertexID [[vertex_id]],
|
||||
constant Vertex2D *vertexArray [[buffer(BVI_Vertices)]],
|
||||
constant vector_uint2 *viewportSizePointer [[buffer(BVI_ViewportSize)]])
|
||||
{
|
||||
RasterizerData out;
|
||||
|
||||
// Index into our array of positions to get the current vertex
|
||||
// Our positions are specified in pixel dimensions (i.e. a value of 100 is 100 pixels from
|
||||
// the origin)
|
||||
float2 pixelSpacePosition = vertexArray[vertexID].position.xy;
|
||||
|
||||
// Get the size of the drawable so that we can convert to normalized device coordinates,
|
||||
float2 viewportSize = float2(*viewportSizePointer);
|
||||
|
||||
// The output position of every vertex shader is in clip space (also known as normalized device
|
||||
// coordinate space, or NDC). A value of (-1.0, -1.0) in clip-space represents the
|
||||
// lower-left corner of the viewport whereas (1.0, 1.0) represents the upper-right corner of
|
||||
// the viewport.
|
||||
|
||||
// In order to convert from positions in pixel space to positions in clip space we divide the
|
||||
// pixel coordinates by half the size of the viewport.
|
||||
out.clipSpacePosition.xy = pixelSpacePosition / (viewportSize / 2.0);
|
||||
|
||||
// Set the z component of our clip space position 0 (since we're only rendering in
|
||||
// 2-Dimensions for this sample)
|
||||
out.clipSpacePosition.z = 0.0;
|
||||
|
||||
// Set the w component to 1.0 since we don't need a perspective divide, which is also not
|
||||
// necessary when rendering in 2-Dimensions
|
||||
out.clipSpacePosition.w = 1.0;
|
||||
|
||||
// Pass our input textureCoordinate straight to our output RasterizerData. This value will be
|
||||
// interpolated with the other textureCoordinate values in the vertices that make up the
|
||||
// triangle.
|
||||
out.textureCoordinate = vertexArray[vertexID].textureCoordinate;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// Fragment function
|
||||
fragment float4 fragmentShader(RasterizerData in [[stage_in]],
|
||||
texture2d<half> colorTexture [[ texture(BTI_InputImage) ]],
|
||||
constant float* brightness [[ buffer(BFI_Brightness) ]])
|
||||
{
|
||||
constexpr sampler textureSampler (mag_filter::linear,
|
||||
min_filter::linear);
|
||||
|
||||
// Sample the texture to obtain a color
|
||||
half4 colorSample = colorTexture.sample(textureSampler, in.textureCoordinate);
|
||||
const half hBrightness = static_cast<half>(*brightness);
|
||||
colorSample.rgb = colorSample.rgb * hBrightness;
|
||||
|
||||
// We return the color of the texture
|
||||
return float4(colorSample);
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
//
|
||||
// TileableRemoteBrightnessShaderTypes.h
|
||||
// beatygril
|
||||
//
|
||||
// Created by dayu on 2026/10/21.
|
||||
//
|
||||
|
||||
#ifndef TileableRemoteBrightnessShaderTypes_h
|
||||
#define TileableRemoteBrightnessShaderTypes_h
|
||||
|
||||
#import <simd/simd.h>
|
||||
|
||||
typedef enum BrightnessVertexInputIndex {
|
||||
BVI_Vertices = 0,
|
||||
BVI_ViewportSize = 1
|
||||
} BrightnessVertexInputIndex;
|
||||
|
||||
typedef enum BrightnessTextureIndex {
|
||||
BTI_InputImage = 0
|
||||
} BrightnessTextureIndex;
|
||||
|
||||
typedef enum BrightnessFragmentIndex {
|
||||
BFI_Brightness = 0
|
||||
} BrightnessFragmentIndex;
|
||||
|
||||
typedef struct Vertex2D {
|
||||
vector_float2 position;
|
||||
vector_float2 textureCoordinate;
|
||||
} Vertex2D;
|
||||
|
||||
|
||||
#endif /* TileableRemoteBrightnessShaderTypes_h */
|
||||
@@ -1,13 +1,11 @@
|
||||
//
|
||||
// beatygrilPlugIn.h
|
||||
// beatygril
|
||||
//
|
||||
// Created by dayu on 2026/10/21.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <FxPlug/FxPlugSDK.h>
|
||||
#import "PGBeautyTypes.h"
|
||||
|
||||
@interface beatygrilPlugIn : NSObject <FxTileableEffect>
|
||||
@property (assign) id<PROAPIAccessing> apiManager;
|
||||
@interface beatygrilPlugIn : NSObject <FxTileableEffect, FxAnalyzer>
|
||||
@property (nonatomic, assign) id<PROAPIAccessing> apiManager;
|
||||
- (PGBeautySettings)settingsAtTime:(CMTime)time;
|
||||
- (void)analyzeForward;
|
||||
- (void)analyzeBackward;
|
||||
- (void)clearTracking;
|
||||
@end
|
||||
@@ -1,320 +1,290 @@
|
||||
//
|
||||
// beatygrilPlugIn.m
|
||||
// beatygril
|
||||
//
|
||||
// Created by dayu on 2026/10/21.
|
||||
//
|
||||
|
||||
#import "beatygrilPlugIn.h"
|
||||
#import <IOSurface/IOSurfaceObjC.h>
|
||||
#import "TileableRemoteBrightnessShaderTypes.h"
|
||||
#import "MetalDeviceCache.h"
|
||||
#import "PGParameterIDs.h"
|
||||
#import "PGBeautyRenderer.h"
|
||||
#import "PGFaceProcessor.h"
|
||||
#import <Metal/Metal.h>
|
||||
#import <IOSurface/IOSurface.h>
|
||||
#import <stddef.h>
|
||||
#import <math.h>
|
||||
|
||||
typedef struct { UInt32 identifier; const char *name; size_t offset; float min, max, step; BOOL percent; } PGSlider;
|
||||
#define PGS(ID, NAME, FIELD, MIN, MAX, STEP, PERCENT) {ID, NAME, offsetof(PGBeautySettings, FIELD), MIN, MAX, STEP, PERCENT}
|
||||
static const PGSlider PGSliders[] = {
|
||||
PGS(PGAmount,"磨皮强度",amount,0,1,.01,YES),
|
||||
PGS(PGMidtone,"中频磨皮",midtoneDetail,0,1,.01,YES),
|
||||
PGS(PGRadius,"磨皮半径",radius,1,30,.5,NO),
|
||||
PGS(PGBrightness,"肤色提亮",skinBrightness,0,1,.01,YES),
|
||||
PGS(PGHue,"肤色色相",skinHue,-30,30,1,NO),
|
||||
PGS(PGSaturation,"肤色饱和度",skinSaturation,-100,100,1,NO),
|
||||
PGS(PGEvenTone,"均匀肤色",skinEvenTone,0,1,.01,YES),
|
||||
PGS(PGTeeth,"牙齿美白",teethWhitening,0,1,.01,YES),
|
||||
PGS(PGEyeBright,"眼睛提亮",eyeBrightening,0,1,.01,YES),
|
||||
PGS(PGHighPass,"高频细节平滑",highPassDetail,0,1,.01,YES),
|
||||
PGS(PGSharpness,"锐化",sharpness,0,1,.01,YES),
|
||||
PGS(PGThin,"瘦脸",faceThin,0,1,.01,YES),
|
||||
PGS(PGBigEye,"大眼",faceBigEye,0,1,.01,YES),
|
||||
PGS(PGShort,"短脸",faceShort,-1,1,.01,NO),
|
||||
PGS(PGNarrow,"窄脸",faceNarrow,-1,1,.01,NO),
|
||||
PGS(PGChin,"下巴",faceChin,-1,1,.01,NO),
|
||||
PGS(PGVFace,"V 脸",faceV,0,1,.01,YES),
|
||||
PGS(PGJaw,"下颌",faceJaw,0,1,.01,YES),
|
||||
PGS(PGCheekbone,"颧骨",faceCheekbone,0,1,.01,YES),
|
||||
PGS(PGSmallHead,"小头",faceSmallHead,0,1,.01,YES),
|
||||
PGS(PGSmallHeadRange,"小头范围",faceSmallHeadRange,0,1,.01,YES),
|
||||
PGS(PGThreshold,"蒙版阈值",maskThreshold,0,255,1,NO),
|
||||
PGS(PGSoftness,"蒙版柔度",maskSoftness,0,1,.01,YES),
|
||||
PGS(PGEdgeProtect,"边缘保护",maskEdgeProtect,0,1,.01,YES),
|
||||
PGS(PGJumpGuard,"防跳脸范围",faceJumpGuardRange,1,4,.05,NO),
|
||||
PGS(PGMix,"整体混合",mix,0,1,.01,YES),
|
||||
};
|
||||
#undef PGS
|
||||
|
||||
static BOOL PGFail(NSError **error, NSString *message) {
|
||||
if (error) *error = [NSError errorWithDomain:FxPlugErrorDomain code:kFxError_InvalidParameter
|
||||
userInfo:@{NSLocalizedDescriptionKey:message}];
|
||||
return NO;
|
||||
}
|
||||
static CIImage *PGImageFromTile(FxImageTile *tile) {
|
||||
if (!tile.ioSurface) return nil;
|
||||
NSDictionary *options = tile.colorSpace ? @{kCIImageColorSpace:(__bridge id)tile.colorSpace} : @{};
|
||||
CIImage *image = [CIImage imageWithIOSurface:(__bridge IOSurfaceRef)tile.ioSurface options:options];
|
||||
if (tile.imageOrigin == kFxImageOrigin_TOP_LEFT) {
|
||||
image = [image imageByApplyingTransform:CGAffineTransformMake(1,0,0,-1,0,image.extent.size.height)];
|
||||
}
|
||||
FxRect b = tile.tilePixelBounds;
|
||||
return [image imageByApplyingTransform:CGAffineTransformMakeTranslation(b.left,b.bottom)];
|
||||
}
|
||||
|
||||
@interface beatygrilPlugIn ()
|
||||
@property (strong) PGBeautyRenderer *beautyRenderer;
|
||||
@property (strong) PGFaceProcessor *faceProcessor;
|
||||
@property (strong) NSMutableDictionary<NSNumber *,CIContext *> *contexts;
|
||||
@property PGBeautySettings analysisSettings;
|
||||
@property CMTime analysisTime;
|
||||
@property NSUInteger analyzedFrames;
|
||||
@property BOOL analyzing;
|
||||
@property (strong) NSArray<NSString *> *loadedTracks;
|
||||
@end
|
||||
|
||||
@implementation beatygrilPlugIn
|
||||
|
||||
//---------------------------------------------------------
|
||||
// initWithAPIManager:
|
||||
//
|
||||
// This method is called when a plug-in is first loaded, and
|
||||
// is a good point to conduct any checks for anti-piracy or
|
||||
// system compatibility. Returning NULL means that a plug-in
|
||||
// chooses not to be accessible for some reason.
|
||||
//---------------------------------------------------------
|
||||
|
||||
- (nullable instancetype)initWithAPIManager:(id<PROAPIAccessing>)newApiManager;
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil)
|
||||
{
|
||||
_apiManager = newApiManager;
|
||||
- (instancetype)initWithAPIManager:(id<PROAPIAccessing>)manager {
|
||||
if ((self=[super init])) {
|
||||
_apiManager=manager;
|
||||
_beautyRenderer=[PGBeautyRenderer new]; _faceProcessor=[PGFaceProcessor new];
|
||||
_contexts=[NSMutableDictionary new]; _analysisSettings=PGBeautyDefaults();
|
||||
_analysisTime=kCMTimeZero;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------
|
||||
// properties
|
||||
//
|
||||
// This method should return an NSDictionary defining the
|
||||
// properties of the effect.
|
||||
//---------------------------------------------------------
|
||||
|
||||
- (BOOL)properties:(NSDictionary * _Nonnull *)properties
|
||||
error:(NSError * _Nullable *)error
|
||||
{
|
||||
*properties = @{
|
||||
kFxPropertyKey_MayRemapTime : [NSNumber numberWithBool:NO],
|
||||
kFxPropertyKey_PixelTransformSupport : [NSNumber numberWithInt:kFxPixelTransform_ScaleTranslate],
|
||||
kFxPropertyKey_VariesWhenParamsAreStatic : [NSNumber numberWithBool:NO]
|
||||
};
|
||||
|
||||
- (BOOL)properties:(NSDictionary **)properties error:(NSError **)error {
|
||||
if (!properties) return PGFail(error,@"宿主未提供属性输出位置。");
|
||||
*properties=@{kFxPropertyKey_MayRemapTime:@NO,
|
||||
kFxPropertyKey_PixelTransformSupport:@(kFxPixelTransform_ScaleTranslate),
|
||||
kFxPropertyKey_VariesWhenParamsAreStatic:@NO,
|
||||
@"NeedsFullBuffer":@YES, @"ChangesOutputSize":@NO,
|
||||
kFxPropertyKey_DesiredProcessingColorInfo:@(kFxImageColorInfo_RGB_GAMMA_VIDEO)};
|
||||
return YES;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------
|
||||
// addParametersWithError
|
||||
//
|
||||
// This method is where a plug-in defines its list of parameters.
|
||||
//---------------------------------------------------------
|
||||
|
||||
- (BOOL)addParametersWithError:(NSError**)error
|
||||
{
|
||||
id<FxParameterCreationAPI_v5> paramAPI = [_apiManager apiForProtocol:@protocol(FxParameterCreationAPI_v5)];
|
||||
if (paramAPI == nil)
|
||||
{
|
||||
NSDictionary* userInfo = @{
|
||||
NSLocalizedDescriptionKey : @"Unable to obtain an FxPlug API Object"
|
||||
};
|
||||
if (error != NULL)
|
||||
*error = [NSError errorWithDomain:FxPlugErrorDomain
|
||||
code:kFxError_APIUnavailable
|
||||
userInfo:userInfo];
|
||||
|
||||
return NO;
|
||||
- (BOOL)addSlider:(UInt32)identifier api:(id<FxParameterCreationAPI_v5>)api {
|
||||
PGBeautySettings defaults=PGBeautyDefaults();
|
||||
for (NSUInteger i=0;i<sizeof(PGSliders)/sizeof(PGSlider);i++) {
|
||||
PGSlider spec=PGSliders[i]; if (spec.identifier!=identifier) continue;
|
||||
NSString *name=[NSString stringWithUTF8String:spec.name];
|
||||
float value=*(float *)((uint8_t *)&defaults+spec.offset);
|
||||
if (spec.percent) return [api addPercentSliderWithName:name parameterID:identifier defaultValue:value parameterMin:spec.min parameterMax:spec.max sliderMin:spec.min sliderMax:spec.max delta:spec.step parameterFlags:kFxParameterFlag_DEFAULT];
|
||||
return [api addFloatSliderWithName:name parameterID:identifier defaultValue:value parameterMin:spec.min parameterMax:spec.max sliderMin:spec.min sliderMax:spec.max delta:spec.step parameterFlags:kFxParameterFlag_DEFAULT];
|
||||
}
|
||||
|
||||
if (![paramAPI addFloatSliderWithName:@"Brightness"
|
||||
parameterID:1
|
||||
defaultValue:1.0
|
||||
parameterMin:0.0
|
||||
parameterMax:100.0
|
||||
sliderMin:0.0
|
||||
sliderMax:10.0
|
||||
delta:0.1
|
||||
parameterFlags:kFxParameterFlag_DEFAULT])
|
||||
{
|
||||
NSDictionary* userInfo = @{
|
||||
NSLocalizedDescriptionKey : @"Unable to add brightness slider"
|
||||
};
|
||||
if (error != NULL)
|
||||
*error = [NSError errorWithDomain:FxPlugErrorDomain
|
||||
code:kFxError_InvalidParameter
|
||||
userInfo:userInfo];
|
||||
|
||||
return NO;
|
||||
}
|
||||
- (BOOL)addParametersWithError:(NSError **)error {
|
||||
id<FxParameterCreationAPI_v5> api=[_apiManager apiForProtocol:@protocol(FxParameterCreationAPI_v5)];
|
||||
if (!api) return PGFail(error,@"无法取得 FxPlug 参数创建接口。");
|
||||
__block BOOL ok=YES;
|
||||
void (^slider)(UInt32)=^(UInt32 identifier){ok=[self addSlider:identifier api:api]&&ok;};
|
||||
void (^toggle)(NSString *,UInt32,BOOL)=^(NSString *name,UInt32 identifier,BOOL value){ok=[api addToggleButtonWithName:name parameterID:identifier defaultValue:value parameterFlags:kFxParameterFlag_DEFAULT]&&ok;};
|
||||
void (^group)(NSString *,UInt32)=^(NSString *name,UInt32 identifier){ok=[api startParameterSubGroup:name parameterID:identifier parameterFlags:kFxParameterFlag_DEFAULT]&&ok;};
|
||||
void (^end)(void)=^{ok=[api endParameterSubGroup]&&ok;};
|
||||
group(@"磨皮",100); toggle(@"启用磨皮",PGSmoothingEnabled,YES);
|
||||
slider(PGAmount); slider(PGMidtone); slider(PGRadius); toggle(@"保护五官",PGFeatureProtect,YES); end();
|
||||
group(@"肤色",101); toggle(@"启用肤色",PGColorEnabled,YES);
|
||||
ok=[api addPopupMenuWithName:@"肤色风格" parameterID:PGTonePreset defaultValue:0 menuEntries:@[@"原色",@"自然白",@"冷白",@"暖白",@"红润白",@"小麦",@"古铜"] parameterFlags:0]&&ok;
|
||||
slider(PGBrightness); slider(PGHue); slider(PGSaturation); slider(PGEvenTone); slider(PGTeeth); slider(PGEyeBright); end();
|
||||
group(@"细节",102); toggle(@"启用细节",PGDetailEnabled,YES); slider(PGHighPass); slider(PGSharpness); end();
|
||||
group(@"脸型",103); toggle(@"启用脸型",PGShapeEnabled,YES);
|
||||
UInt32 shape[]={PGThin,PGBigEye,PGShort,PGNarrow,PGChin,PGVFace,PGJaw,PGCheekbone,PGSmallHead,PGSmallHeadRange};
|
||||
for(NSUInteger i=0;i<sizeof(shape)/sizeof(UInt32);i++) slider(shape[i]); end();
|
||||
group(@"人脸选择与跟踪",104); toggle(@"只处理选中人脸",PGSingleFace,NO);
|
||||
ok=[api addPointParameterWithName:@"选脸位置" parameterID:PGSelection defaultX:.5 defaultY:.5 parameterFlags:0]&&ok;
|
||||
toggle(@"多人高速模式",PGFastMulti,NO); slider(PGJumpGuard);
|
||||
toggle(@"使用跟踪数据",PGTrackEnabled,NO);
|
||||
ok=[api addPopupMenuWithName:@"跟踪目标" parameterID:PGTarget defaultValue:0 menuEntries:@[@"目标 1",@"目标 2",@"目标 3"] parameterFlags:kFxParameterFlag_NOT_ANIMATABLE]&&ok;
|
||||
ok=[api addPushButtonWithName:@"向前分析" parameterID:PGAnalyzeForward selector:@selector(analyzeForward) parameterFlags:0]&&ok;
|
||||
ok=[api addPushButtonWithName:@"向后分析" parameterID:PGAnalyzeBackward selector:@selector(analyzeBackward) parameterFlags:0]&&ok;
|
||||
ok=[api addPushButtonWithName:@"清除当前目标" parameterID:PGClearTrack selector:@selector(clearTracking) parameterFlags:0]&&ok;
|
||||
ok=[api addStringParameterWithName:@"跟踪状态" parameterID:PGStatus defaultValue:@"选择画面中的人脸后开始分析" parameterFlags:kFxParameterFlag_NOT_ANIMATABLE|kFxParameterFlag_DISABLED]&&ok;
|
||||
toggle(@"显示人脸框",PGFaceBoxes,NO); toggle(@"显示跟踪框",PGTrackBoxes,NO); end();
|
||||
group(@"蒙版与输出",105); slider(PGThreshold); slider(PGSoftness); slider(PGEdgeProtect);
|
||||
toggle(@"预览蒙版",PGShowMask,NO); toggle(@"反转蒙版",PGInvertMask,NO);
|
||||
ok=[api addPopupMenuWithName:@"蒙版通道" parameterID:PGDebugChannel defaultValue:0 menuEntries:@[@"皮肤",@"牙齿",@"眼睛"] parameterFlags:0]&&ok;
|
||||
slider(PGMix); end();
|
||||
for(UInt32 identifier=PGTrackData1;identifier<=PGTrackData3;identifier++)
|
||||
ok=[api addStringParameterWithName:@"分析数据" parameterID:identifier defaultValue:@"" parameterFlags:kFxParameterFlag_HIDDEN|kFxParameterFlag_NOT_ANIMATABLE]&&ok;
|
||||
return ok ? YES : PGFail(error,@"宿主未能创建完整的人像美化参数。");
|
||||
}
|
||||
- (PGBeautySettings)settingsAtTime:(CMTime)time {
|
||||
PGBeautySettings s=PGBeautyDefaults();
|
||||
id<FxParameterRetrievalAPI_v6> api=[_apiManager apiForProtocol:@protocol(FxParameterRetrievalAPI_v6)];
|
||||
if(!api) return s;
|
||||
for(NSUInteger i=0;i<sizeof(PGSliders)/sizeof(PGSlider);i++) {
|
||||
PGSlider spec=PGSliders[i]; double value=*(float *)((uint8_t *)&s+spec.offset);
|
||||
if([api getFloatValue:&value fromParameter:spec.identifier atTime:time] && isfinite(value))
|
||||
*(float *)((uint8_t *)&s+spec.offset)=(float)fmin(spec.max,fmax(spec.min,value));
|
||||
}
|
||||
|
||||
struct {UInt32 identifier;size_t offset;} toggles[]={
|
||||
{PGFeatureProtect,offsetof(PGBeautySettings,featureProtection)}, {PGShowMask,offsetof(PGBeautySettings,showMask)},
|
||||
{PGInvertMask,offsetof(PGBeautySettings,invertMask)}, {PGFaceBoxes,offsetof(PGBeautySettings,showFaceBoxes)},
|
||||
{PGTrackBoxes,offsetof(PGBeautySettings,showTrackBox)}, {PGSingleFace,offsetof(PGBeautySettings,singleFace)},
|
||||
{PGFastMulti,offsetof(PGBeautySettings,fastMultiFace)}, {PGTrackEnabled,offsetof(PGBeautySettings,trackingEnabled)}};
|
||||
for(NSUInteger i=0;i<sizeof(toggles)/sizeof(toggles[0]);i++) {
|
||||
BOOL value=*(uint32_t *)((uint8_t *)&s+toggles[i].offset)!=0;
|
||||
[api getBoolValue:&value fromParameter:toggles[i].identifier atTime:time];
|
||||
*(uint32_t *)((uint8_t *)&s+toggles[i].offset)=value;
|
||||
}
|
||||
int value=0; [api getIntValue:&value fromParameter:PGTonePreset atTime:time]; s.skinTonePreset=MIN(6,MAX(0,value));
|
||||
value=0; [api getIntValue:&value fromParameter:PGDebugChannel atTime:time]; s.debugChannel=MIN(2,MAX(0,value));
|
||||
value=0; [api getIntValue:&value fromParameter:PGTarget atTime:time]; s.targetIndex=MIN(2,MAX(0,value));
|
||||
double x=.5,y=.5; [api getXValue:&x YValue:&y fromParameter:PGSelection atTime:time];
|
||||
s.selectionX=isfinite(x)?fmin(1,fmax(0,x)):.5; s.selectionY=isfinite(y)?fmin(1,fmax(0,y)):.5;
|
||||
BOOL enabled=YES;
|
||||
[api getBoolValue:&enabled fromParameter:PGSmoothingEnabled atTime:time]; if(!enabled) s.amount=s.midtoneDetail=0;
|
||||
enabled=YES; [api getBoolValue:&enabled fromParameter:PGColorEnabled atTime:time];
|
||||
if(!enabled) {s.skinBrightness=s.skinHue=s.skinSaturation=s.skinEvenTone=s.teethWhitening=s.eyeBrightening=0;s.skinTonePreset=0;}
|
||||
enabled=YES; [api getBoolValue:&enabled fromParameter:PGDetailEnabled atTime:time]; if(!enabled) s.highPassDetail=s.sharpness=0;
|
||||
enabled=YES; [api getBoolValue:&enabled fromParameter:PGShapeEnabled atTime:time];
|
||||
if(!enabled) s.faceThin=s.faceBigEye=s.faceShort=s.faceNarrow=s.faceChin=s.faceV=s.faceJaw=s.faceCheekbone=s.faceSmallHead=0;
|
||||
if(s.fastMultiFace) s.singleFace=0;
|
||||
return s;
|
||||
}
|
||||
- (NSArray<NSString *> *)trackStrings {
|
||||
id<FxParameterRetrievalAPI_v6> api=[_apiManager apiForProtocol:@protocol(FxParameterRetrievalAPI_v6)];
|
||||
NSMutableArray *tracks=[NSMutableArray new];
|
||||
for(UInt32 identifier=PGTrackData1;identifier<=PGTrackData3;identifier++) {
|
||||
NSString *value=@"";[api getStringParameterValue:&value fromParameter:identifier];
|
||||
[tracks addObject:value?:@""];
|
||||
}
|
||||
return tracks;
|
||||
}
|
||||
- (BOOL)pluginState:(NSData **)state atTime:(CMTime)time quality:(FxQuality)quality error:(NSError **)error {
|
||||
if(!state) return PGFail(error,@"缺少渲染状态输出位置。");
|
||||
if(![_apiManager apiForProtocol:@protocol(FxParameterRetrievalAPI_v6)]) return PGFail(error,@"无法读取宿主参数。");
|
||||
PGBeautySettings s=[self settingsAtTime:time];
|
||||
@synchronized(self) {_analysisTime=time;}
|
||||
*state=[NSPropertyListSerialization dataWithPropertyList:@{@"settings":[NSData dataWithBytes:&s length:sizeof(s)],@"tracks":[self trackStrings]} format:NSPropertyListBinaryFormat_v1_0 options:0 error:error];
|
||||
return *state!=nil;
|
||||
}
|
||||
- (BOOL)destinationImageRect:(FxRect *)rect sourceImages:(NSArray<FxImageTile *> *)sources destinationImage:(FxImageTile *)destination pluginState:(NSData *)state atTime:(CMTime)time error:(NSError **)error {
|
||||
if(!rect||sources.count==0) return PGFail(error,@"缺少输入图像。");
|
||||
*rect=sources.firstObject.imagePixelBounds;return YES;
|
||||
}
|
||||
- (BOOL)sourceTileRect:(FxRect *)rect sourceImageIndex:(NSUInteger)index sourceImages:(NSArray<FxImageTile *> *)sources destinationTileRect:(FxRect)destinationRect destinationImage:(FxImageTile *)destination pluginState:(NSData *)state atTime:(CMTime)time error:(NSError **)error {
|
||||
if(!rect||index>=sources.count) return PGFail(error,@"输入图像索引无效。");
|
||||
*rect=sources[index].imagePixelBounds;return YES;
|
||||
}
|
||||
- (CIContext *)contextForTile:(FxImageTile *)tile {
|
||||
NSNumber *key=@(tile.deviceRegistryID);
|
||||
@synchronized(_contexts) {
|
||||
if(!_contexts[key]) {
|
||||
id<MTLDevice> device=nil;
|
||||
for(id<MTLDevice> candidate in MTLCopyAllDevices()) if(candidate.registryID==tile.deviceRegistryID) {device=candidate;break;}
|
||||
if(!device) device=MTLCreateSystemDefaultDevice();
|
||||
if(!device) return nil;
|
||||
_contexts[key]=[CIContext contextWithMTLDevice:device options:@{kCIContextCacheIntermediates:@NO}];
|
||||
}
|
||||
return _contexts[key];
|
||||
}
|
||||
}
|
||||
- (BOOL)renderDestinationImage:(FxImageTile *)destination sourceImages:(NSArray<FxImageTile *> *)sources pluginState:(NSData *)state atTime:(CMTime)time error:(NSError **)error {
|
||||
@autoreleasepool {
|
||||
if(sources.count==0||!sources.firstObject.ioSurface||!destination.ioSurface||state.length>16*1024*1024) return PGFail(error,@"宿主传入的画面或状态无效。");
|
||||
NSDictionary *plist=[NSPropertyListSerialization propertyListWithData:state options:NSPropertyListImmutable format:nil error:error];
|
||||
if(![plist isKindOfClass:NSDictionary.class]||![plist[@"settings"] isKindOfClass:NSData.class]||[plist[@"settings"] length]!=sizeof(PGBeautySettings)) return PGFail(error,@"渲染状态版本无效。");
|
||||
PGBeautySettings s;[plist[@"settings"] getBytes:&s length:sizeof(s)];
|
||||
if(s.version!=1) return PGFail(error,@"此版本不支持保存的渲染状态。");
|
||||
CIImage *input=PGImageFromTile(sources.firstObject); if(!input) return PGFail(error,@"无法读取 IOSurface。");
|
||||
CIContext *context=[self contextForTile:destination]; if(!context) return PGFail(error,@"没有可用的 Metal 图形设备。");
|
||||
CIImage *output=nil;
|
||||
@synchronized(_faceProcessor) {
|
||||
NSArray *tracks=plist[@"tracks"];
|
||||
if(![tracks isKindOfClass:NSArray.class]||tracks.count!=3) return PGFail(error,@"跟踪状态无效。");
|
||||
if(![_loadedTracks isEqualToArray:tracks]) {
|
||||
for(NSUInteger i=0;i<3;i++) {
|
||||
if(![tracks[i] isKindOfClass:NSString.class]) return PGFail(error,@"跟踪记录无效。");
|
||||
[_faceProcessor setTrackingData:[[NSData alloc] initWithBase64EncodedString:tracks[i] options:0] forTarget:i];
|
||||
}
|
||||
_loadedTracks=[tracks copy];
|
||||
}
|
||||
PGFaceFrame *frame=[_faceProcessor processImage:input settings:s time:time error:error];
|
||||
if(!frame) return NO;
|
||||
output=[_beautyRenderer processImage:frame.image settings:s skinMask:frame.skinMask teethMask:frame.teethMask eyeMask:frame.eyeMask error:error];
|
||||
}
|
||||
if(!output) return NO;
|
||||
// Blend from the original frame so zero mix also disables geometry.
|
||||
if(s.mix<1 && !s.showMask) {
|
||||
output=[output imageByApplyingFilter:@"CIDissolveTransition" withInputParameters:@{kCIInputTargetImageKey:input,kCIInputTimeKey:@(1-s.mix)}];
|
||||
}
|
||||
FxRect b=destination.tilePixelBounds;
|
||||
output=[output imageByApplyingTransform:CGAffineTransformMakeTranslation(-b.left,-b.bottom)];
|
||||
size_t width=IOSurfaceGetWidth((__bridge IOSurfaceRef)destination.ioSurface),height=IOSurfaceGetHeight((__bridge IOSurfaceRef)destination.ioSurface);
|
||||
if(destination.imageOrigin==kFxImageOrigin_TOP_LEFT) output=[output imageByApplyingTransform:CGAffineTransformMake(1,0,0,-1,0,height)];
|
||||
@try {[context render:output toIOSurface:(__bridge IOSurfaceRef)destination.ioSurface bounds:CGRectMake(0,0,width,height) colorSpace:destination.colorSpace];}
|
||||
@catch(NSException *exception) {return PGFail(error,exception.reason?:@"图像处理失败。");}
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------
|
||||
// pluginState:atTime:quality:error
|
||||
//
|
||||
// Your plug-in should get its parameter values, do any calculations it needs to
|
||||
// from those values, and package up the result to be used later with rendering.
|
||||
// The host application will call this method before rendering. The
|
||||
// FxParameterRetrievalAPI* is valid during this call. Use it to get the values of
|
||||
// your plug-in's parameters, then put those values or the results of any calculations
|
||||
// you need to do with those parameters to render into an NSData that you return
|
||||
// to the host application. The host will pass it back to you during subsequent calls.
|
||||
// Do not re-use the NSData; always create a new one as this method may be called
|
||||
// on multiple threads at the same time.
|
||||
//---------------------------------------------------------
|
||||
|
||||
- (BOOL)pluginState:(NSData**)pluginState
|
||||
atTime:(CMTime)renderTime
|
||||
quality:(FxQuality)qualityLevel
|
||||
error:(NSError**)error
|
||||
{
|
||||
BOOL succeeded = NO;
|
||||
id<FxParameterRetrievalAPI_v6> paramGetAPI = [_apiManager apiForProtocol:@protocol(FxParameterRetrievalAPI_v6)];
|
||||
if (paramGetAPI != nil)
|
||||
{
|
||||
double brightness = 1.0;
|
||||
[paramGetAPI getFloatValue:&brightness
|
||||
fromParameter:1
|
||||
atTime:renderTime];
|
||||
|
||||
*pluginState = [NSData dataWithBytes:&brightness
|
||||
length:sizeof(brightness)];
|
||||
|
||||
if (*pluginState != nil)
|
||||
{
|
||||
succeeded = YES;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (error != NULL)
|
||||
*error = [NSError errorWithDomain:FxPlugErrorDomain
|
||||
code:kFxError_ThirdPartyDeveloperStart + 20
|
||||
userInfo:@{
|
||||
NSLocalizedDescriptionKey :
|
||||
@"Unable to retrieve FxParameterRetrievalAPI_v6 in \
|
||||
[-pluginStateAtTime:]" }];
|
||||
}
|
||||
|
||||
return succeeded;
|
||||
- (void)setStatus:(NSString *)status {
|
||||
id<FxParameterSettingAPI_v5> api=[_apiManager apiForProtocol:@protocol(FxParameterSettingAPI_v5)];
|
||||
[api setStringParameterValue:status toParameter:PGStatus];
|
||||
}
|
||||
|
||||
//---------------------------------------------------------
|
||||
// destinationImageRect:sourceImages:destinationImage:pluginState:atTime:error
|
||||
//
|
||||
// This method will calculate the rectangular bounds of the output
|
||||
// image given the various inputs and plug-in state
|
||||
// at the given render time.
|
||||
// It will pass in an array of images, the plug-in state
|
||||
// returned from your plug-in's -pluginStateAtTime:error: method,
|
||||
// and the render time.
|
||||
//---------------------------------------------------------
|
||||
|
||||
- (BOOL)destinationImageRect:(FxRect *)destinationImageRect
|
||||
sourceImages:(NSArray<FxImageTile *> *)sourceImages
|
||||
destinationImage:(nonnull FxImageTile *)destinationImage
|
||||
pluginState:(NSData *)pluginState
|
||||
atTime:(CMTime)renderTime
|
||||
error:(NSError * _Nullable *)outError
|
||||
{
|
||||
if (sourceImages.count < 1)
|
||||
{
|
||||
NSLog (@"No inputImages list");
|
||||
return NO;
|
||||
}
|
||||
|
||||
// In the case of a filter that only changed RGB values,
|
||||
// the output rect is the same as the input rect.
|
||||
*destinationImageRect = sourceImages [ 0 ].imagePixelBounds;
|
||||
|
||||
return YES;
|
||||
|
||||
- (void)startAnalysisForward:(BOOL)forward {
|
||||
NSError *error=nil;
|
||||
PGBeautySettings s=[self settingsAtTime:_analysisTime];
|
||||
@synchronized(_faceProcessor) {[_faceProcessor setTrackingData:nil forTarget:s.targetIndex];}
|
||||
_analysisSettings=s;_analysisSettings.trackingEnabled=1;_analysisSettings.singleFace=1;
|
||||
id<FxAnalysisAPI> api=[_apiManager apiForProtocol:@protocol(FxAnalysisAPI)];
|
||||
BOOL ok=forward?[api startForwardAnalysis:kFxAnalysisLocation_CPU error:&error]:[api startBackwardAnalysis:kFxAnalysisLocation_CPU error:&error];
|
||||
[self setStatus:ok?@"正在分析所选人脸…":(error.localizedDescription?:@"当前宿主不提供帧分析接口。")];
|
||||
}
|
||||
|
||||
//---------------------------------------------------------
|
||||
// sourceTileRect:sourceImageIndex:sourceImages:destinationTileRect:destinationImage:pluginState:atTime:error
|
||||
//
|
||||
// Calculate tile of the source image we need
|
||||
// to render the given output tile.
|
||||
//---------------------------------------------------------
|
||||
|
||||
- (BOOL)sourceTileRect:(FxRect *)sourceTileRect
|
||||
sourceImageIndex:(NSUInteger)sourceImageIndex
|
||||
sourceImages:(NSArray<FxImageTile *> *)sourceImages
|
||||
destinationTileRect:(FxRect)destinationTileRect
|
||||
destinationImage:(FxImageTile *)destinationImage
|
||||
pluginState:(NSData *)pluginState
|
||||
atTime:(CMTime)renderTime
|
||||
error:(NSError * _Nullable *)outError
|
||||
{
|
||||
// Since this is a color-only filter, the input tile will be the same size as the output tile
|
||||
*sourceTileRect = destinationTileRect;
|
||||
|
||||
return YES;
|
||||
- (void)analyzeForward {[self startAnalysisForward:YES];}
|
||||
- (void)analyzeBackward {[self startAnalysisForward:NO];}
|
||||
- (void)clearTracking {
|
||||
PGBeautySettings s=[self settingsAtTime:_analysisTime];
|
||||
@synchronized(_faceProcessor) {[_faceProcessor setTrackingData:nil forTarget:s.targetIndex];_loadedTracks=nil;}
|
||||
id<FxParameterSettingAPI_v5> api=[_apiManager apiForProtocol:@protocol(FxParameterSettingAPI_v5)];
|
||||
[api setStringParameterValue:@"" toParameter:PGTrackData1+s.targetIndex];
|
||||
[self setStatus:@"已清除当前目标的分析数据"];
|
||||
}
|
||||
|
||||
//---------------------------------------------------------
|
||||
// renderDestinationImage:sourceImages:pluginState:atTime:error:
|
||||
//
|
||||
// The host will call this method when it wants your plug-in to render an image
|
||||
// tile of the output image. It will pass in each of the input tiles needed as well
|
||||
// as the plug-in state needed for the calculations. Your plug-in should do all its
|
||||
// rendering in this method. It should not attempt to use the FxParameterRetrievalAPI*
|
||||
// object as it is invalid at this time. Note that this method will be called on
|
||||
// multiple threads at the same time.
|
||||
//---------------------------------------------------------
|
||||
|
||||
- (BOOL)renderDestinationImage:(FxImageTile *)destinationImage
|
||||
sourceImages:(NSArray<FxImageTile *> *)sourceImages
|
||||
pluginState:(NSData *)pluginState
|
||||
atTime:(CMTime)renderTime
|
||||
error:(NSError * _Nullable *)outError
|
||||
{
|
||||
if ((pluginState == nil) || (sourceImages [ 0 ].ioSurface == nil) || (destinationImage.ioSurface == nil))
|
||||
{
|
||||
NSDictionary* userInfo = @{
|
||||
NSLocalizedDescriptionKey : @"Invalid plugin state received from host"
|
||||
};
|
||||
if (outError != NULL)
|
||||
*outError = [NSError errorWithDomain:FxPlugErrorDomain
|
||||
code:kFxError_InvalidParameter
|
||||
userInfo:userInfo];
|
||||
return NO;
|
||||
- (BOOL)desiredAnalysisTimeRange:(CMTimeRange *)desired forInputWithTimeRange:(CMTimeRange)range error:(NSError **)error {
|
||||
if(!desired) return PGFail(error,@"缺少分析时间范围。");*desired=range;return YES;
|
||||
}
|
||||
- (BOOL)setupAnalysisForTimeRange:(CMTimeRange)range frameDuration:(CMTime)duration error:(NSError **)error {
|
||||
_analyzedFrames=0;_analyzing=YES;return YES;
|
||||
}
|
||||
- (BOOL)analyzeFrame:(FxImageTile *)frame atTime:(CMTime)time error:(NSError **)error {
|
||||
CIImage *image=PGImageFromTile(frame);if(!image) return PGFail(error,@"无法读取分析帧。");
|
||||
BOOL ok=NO;
|
||||
@synchronized(_faceProcessor) {ok=[_faceProcessor analyzeImage:image settings:_analysisSettings time:time error:error];}
|
||||
if(ok) _analyzedFrames++;
|
||||
return ok;
|
||||
}
|
||||
- (BOOL)cleanupAnalysis:(NSError **)error {
|
||||
id<FxParameterSettingAPI_v5> api=[_apiManager apiForProtocol:@protocol(FxParameterSettingAPI_v5)];
|
||||
NSData *data=nil;
|
||||
@synchronized(_faceProcessor) {data=[_faceProcessor trackingDataForTarget:_analysisSettings.targetIndex];_loadedTracks=nil;}
|
||||
if(data) {
|
||||
if(![api setStringParameterValue:[data base64EncodedStringWithOptions:0] toParameter:PGTrackData1+_analysisSettings.targetIndex]) return PGFail(error,@"无法将分析数据保存到工程。");
|
||||
[api setBoolValue:YES toParameter:PGTrackEnabled atTime:_analysisTime];
|
||||
}
|
||||
|
||||
// This is where you would access parameter values and other info about the source tile
|
||||
// from the pluginState.
|
||||
double brightness = 0.0;
|
||||
[pluginState getBytes:&brightness
|
||||
length:sizeof(brightness)];
|
||||
|
||||
// Set up the renderer, in this case we are using Metal.
|
||||
|
||||
MetalDeviceCache* deviceCache = [MetalDeviceCache deviceCache];
|
||||
MTLPixelFormat pixelFormat = [MetalDeviceCache MTLPixelFormatForImageTile:destinationImage];
|
||||
id<MTLCommandQueue> commandQueue = [deviceCache commandQueueWithRegistryID:sourceImages[0].deviceRegistryID
|
||||
pixelFormat:pixelFormat];
|
||||
if (commandQueue == nil)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer];
|
||||
commandBuffer.label = @"DynamicRegXPC Command Buffer";
|
||||
[commandBuffer enqueue];
|
||||
|
||||
id<MTLTexture> inputTexture = [sourceImages[0] metalTextureForDevice:[deviceCache deviceWithRegistryID:sourceImages[0].deviceRegistryID]];
|
||||
id<MTLTexture> outputTexture = [destinationImage metalTextureForDevice:[deviceCache deviceWithRegistryID:destinationImage.deviceRegistryID]];
|
||||
|
||||
MTLRenderPassColorAttachmentDescriptor* colorAttachmentDescriptor = [[MTLRenderPassColorAttachmentDescriptor alloc] init];
|
||||
colorAttachmentDescriptor.texture = outputTexture;
|
||||
colorAttachmentDescriptor.clearColor = MTLClearColorMake(1.0, 0.5, 0.0, 1.0);
|
||||
colorAttachmentDescriptor.loadAction = MTLLoadActionClear;
|
||||
MTLRenderPassDescriptor* renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
|
||||
renderPassDescriptor.colorAttachments [ 0 ] = colorAttachmentDescriptor;
|
||||
id<MTLRenderCommandEncoder> commandEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
|
||||
|
||||
// Rendering
|
||||
float outputWidth = (float)(destinationImage.tilePixelBounds.right - destinationImage.tilePixelBounds.left);
|
||||
float outputHeight = (float)(destinationImage.tilePixelBounds.top - destinationImage.tilePixelBounds.bottom);
|
||||
Vertex2D vertices[] = {
|
||||
{ { outputWidth / 2.0, -outputHeight / 2.0 }, { 1.0, 1.0 } },
|
||||
{ { -outputWidth / 2.0, -outputHeight / 2.0 }, { 0.0, 1.0 } },
|
||||
{ { outputWidth / 2.0, outputHeight / 2.0 }, { 1.0, 0.0 } },
|
||||
{ { -outputWidth / 2.0, outputHeight / 2.0 }, { 0.0, 0.0 } }
|
||||
};
|
||||
|
||||
MTLViewport viewport = {
|
||||
0, 0, outputWidth, outputHeight, -1.0, 1.0
|
||||
};
|
||||
[commandEncoder setViewport:viewport];
|
||||
|
||||
id<MTLRenderPipelineState> pipelineState = [deviceCache pipelineStateWithRegistryID:sourceImages[0].deviceRegistryID
|
||||
pixelFormat:pixelFormat];
|
||||
[commandEncoder setRenderPipelineState:pipelineState];
|
||||
|
||||
[commandEncoder setVertexBytes:vertices
|
||||
length:sizeof(vertices)
|
||||
atIndex:BVI_Vertices];
|
||||
|
||||
simd_uint2 viewportSize = {
|
||||
(unsigned int)(outputWidth),
|
||||
(unsigned int)(outputHeight)
|
||||
};
|
||||
[commandEncoder setVertexBytes:&viewportSize
|
||||
length:sizeof(viewportSize)
|
||||
atIndex:BVI_ViewportSize];
|
||||
|
||||
[commandEncoder setFragmentTexture:inputTexture
|
||||
atIndex:BTI_InputImage];
|
||||
|
||||
float fragmentBrightness = (float)brightness;
|
||||
[commandEncoder setFragmentBytes:&fragmentBrightness
|
||||
length:sizeof(fragmentBrightness)
|
||||
atIndex:BFI_Brightness];
|
||||
|
||||
[commandEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip
|
||||
vertexStart:0
|
||||
vertexCount:4];
|
||||
|
||||
[commandEncoder endEncoding];
|
||||
[commandBuffer commit];
|
||||
|
||||
[commandBuffer waitUntilCompleted];
|
||||
|
||||
[colorAttachmentDescriptor release];
|
||||
|
||||
[deviceCache returnCommandQueueToCache:commandQueue];
|
||||
|
||||
return YES;
|
||||
|
||||
[self setStatus:[NSString stringWithFormat:@"目标 %u:已分析 %lu 帧",_analysisSettings.targetIndex+1,(unsigned long)_analyzedFrames]];
|
||||
_analyzing=NO;return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Save only explicitly listed project files, under the shared dayu Git lock."""
|
||||
import argparse, hashlib, json, os, pathlib, subprocess, time
|
||||
p=argparse.ArgumentParser();p.add_argument('message');p.add_argument('files',nargs='+');p.add_argument('--tag');a=p.parse_args()
|
||||
root=pathlib.Path(__file__).resolve().parent.parent
|
||||
run=lambda *args,**kw:subprocess.run(args,cwd=root,check=True,text=True,**kw)
|
||||
remote=run('git','remote','get-url','origin',capture_output=True).stdout.strip()
|
||||
if not remote.startswith('https://dayu.media/git/') or '@' in remote:raise SystemExit('Unexpected project remote')
|
||||
lock=pathlib.Path('/Users/dayu/.codex/git-backup-locks')/(hashlib.sha256(remote.lower().rstrip('/').encode()).hexdigest()+'.lock')
|
||||
lock.parent.mkdir(parents=True,exist_ok=True)
|
||||
for n in range(120):
|
||||
try:lock.mkdir();break
|
||||
except FileExistsError:
|
||||
if n==119:raise SystemExit('Repository lock still held; inspect owner.json')
|
||||
time.sleep(.5)
|
||||
(lock/'owner.json').write_text(json.dumps({'pid':os.getpid(),'cwd':str(root),'remote':remote}))
|
||||
try:
|
||||
run('git','add','--',*a.files)
|
||||
run('git','diff','--cached','--check')
|
||||
run('git','diff','--cached','--stat')
|
||||
changed=subprocess.run(['git','diff','--cached','--quiet'],cwd=root).returncode
|
||||
if changed:run('git','commit','-m',a.message)
|
||||
branch=run('git','branch','--show-current',capture_output=True).stdout.strip()
|
||||
if not branch:raise SystemExit('Detached HEAD: create a durable branch before saving')
|
||||
run('git','push','-u','origin',branch)
|
||||
sha=run('git','rev-parse','HEAD',capture_output=True).stdout.strip()
|
||||
observed=run('git','ls-remote','origin','refs/heads/'+branch,capture_output=True).stdout.split()[0]
|
||||
if observed!=sha:raise SystemExit('Remote checkpoint SHA mismatch')
|
||||
if a.tag:
|
||||
run('git','tag',a.tag)
|
||||
run('git','push','origin','refs/tags/'+a.tag)
|
||||
observed=run('git','ls-remote','origin','refs/tags/'+a.tag,capture_output=True).stdout.split()[0]
|
||||
if observed!=sha:raise SystemExit('Remote tag SHA mismatch')
|
||||
print(json.dumps({'branch':branch,'sha':sha,'remote_verified':True,'tag':a.tag}))
|
||||
finally:
|
||||
(lock/'owner.json').unlink();lock.rmdir()
|
||||
在新工单中引用
屏蔽一个用户