diff --git a/Sources/YUCIFilterConstructor.h b/Sources/YUCIFilterConstructor.h index 4fa9bc6..f155ac6 100644 --- a/Sources/YUCIFilterConstructor.h +++ b/Sources/YUCIFilterConstructor.h @@ -8,6 +8,8 @@ #import #import + +NS_ASSUME_NONNULL_BEGIN /* Using class with name `filterName` to construct a filter object. */ @@ -19,3 +21,5 @@ - (instancetype)init NS_UNAVAILABLE; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Sources/YUCIHighPass.h b/Sources/YUCIHighPass.h index 9004391..bc10461 100644 --- a/Sources/YUCIHighPass.h +++ b/Sources/YUCIHighPass.h @@ -10,8 +10,8 @@ @interface YUCIHighPass : CIFilter -@property (nonatomic,strong) CIImage *inputImage; +@property (nonatomic, strong, nullable) CIImage *inputImage; -@property (nonatomic,copy) NSNumber *inputRadius; //default 1.0 +@property (nonatomic, copy, null_resettable) NSNumber *inputRadius; //default 1.0 @end diff --git a/Sources/YUCIHighPassSkinSmoothing.h b/Sources/YUCIHighPassSkinSmoothing.h index 7d95a61..47ed244 100644 --- a/Sources/YUCIHighPassSkinSmoothing.h +++ b/Sources/YUCIHighPassSkinSmoothing.h @@ -10,12 +10,12 @@ @interface YUCIHighPassSkinSmoothing : CIFilter -@property (nonatomic,strong) CIImage *inputImage; +@property (nonatomic, strong, nullable) CIImage *inputImage; -@property (nonatomic,copy) NSNumber *inputAmount; //default: 0.75 +@property (nonatomic, copy, null_resettable) NSNumber *inputAmount; //default: 0.75 -@property (nonatomic,copy) NSNumber *inputRadius; //default: 8.0 +@property (nonatomic, copy, null_resettable) NSNumber *inputRadius; //default: 8.0 -@property (nonatomic,copy) NSArray *inputToneCurveControlPoints; //default: (0,0) (120/255.0,146/255.0) (1,1) +@property (nonatomic, copy, null_resettable) NSArray *inputToneCurveControlPoints; //default: (0,0) (120/255.0,146/255.0) (1,1) @end diff --git a/Sources/YUCIHighPassSkinSmoothing.m b/Sources/YUCIHighPassSkinSmoothing.m index 5c728fd..d8ce369 100644 --- a/Sources/YUCIHighPassSkinSmoothing.m +++ b/Sources/YUCIHighPassSkinSmoothing.m @@ -134,25 +134,32 @@ - (NSNumber *)inputRadius { - (YUCIRGBToneCurve *)skinToneCurveFilter { if (!_skinToneCurveFilter) { _skinToneCurveFilter = [[YUCIRGBToneCurve alloc] init]; - _skinToneCurveFilter.rgbCompositeControlPoints = @[[CIVector vectorWithX:0 Y:0], - [CIVector vectorWithX:120/255.0 Y:146/255.0], - [CIVector vectorWithX:1.0 Y:1.0]]; + _skinToneCurveFilter.inputRGBCompositeControlPoints = self.defaultInputRGBCompositeControlPoints; } return _skinToneCurveFilter; } +- (NSArray *)defaultInputRGBCompositeControlPoints { + return @[[CIVector vectorWithX:0 Y:0], + [CIVector vectorWithX:120/255.0 Y:146/255.0], + [CIVector vectorWithX:1.0 Y:1.0]]; +} + - (void)setInputToneCurveControlPoints:(NSArray *)inputToneCurveControlPoints { - self.skinToneCurveFilter.rgbCompositeControlPoints = inputToneCurveControlPoints; + if (inputToneCurveControlPoints.count == 0) { + inputToneCurveControlPoints = self.defaultInputRGBCompositeControlPoints; + } + self.skinToneCurveFilter.inputRGBCompositeControlPoints = inputToneCurveControlPoints; } - (NSArray *)inputToneCurveControlPoints { - return self.skinToneCurveFilter.rgbCompositeControlPoints; + return self.skinToneCurveFilter.inputRGBCompositeControlPoints; } - (void)setDefaults { self.inputAmount = nil; self.inputRadius = nil; - self.skinToneCurveFilter = nil; + self.inputToneCurveControlPoints = nil; } - (CIImage *)outputImage { diff --git a/Sources/YUCIMetalUtilities.h b/Sources/YUCIMetalUtilities.h index d10b63c..b9e6ee4 100644 --- a/Sources/YUCIMetalUtilities.h +++ b/Sources/YUCIMetalUtilities.h @@ -10,10 +10,14 @@ #import #import +NS_ASSUME_NONNULL_BEGIN + @interface YUCIMetalUtilities : NSObject + (id)textureFromCGImage:(CGImageRef)imageRef device:(id)device; @end +NS_ASSUME_NONNULL_END + #endif diff --git a/Sources/YUCIRGBToneCurve.h b/Sources/YUCIRGBToneCurve.h index 723c4fd..aea1d91 100644 --- a/Sources/YUCIRGBToneCurve.h +++ b/Sources/YUCIRGBToneCurve.h @@ -10,13 +10,13 @@ @interface YUCIRGBToneCurve : CIFilter -@property (nonatomic,strong) CIImage *inputImage; +@property (nonatomic, strong, nullable) CIImage *inputImage; -@property(nonatomic, copy) NSArray *redControlPoints; -@property(nonatomic, copy) NSArray *greenControlPoints; -@property(nonatomic, copy) NSArray *blueControlPoints; -@property(nonatomic, copy) NSArray *rgbCompositeControlPoints; +@property (nonatomic, copy, null_resettable) NSArray *inputRedControlPoints; +@property (nonatomic, copy, null_resettable) NSArray *inputGreenControlPoints; +@property (nonatomic, copy, null_resettable) NSArray *inputBlueControlPoints; +@property (nonatomic, copy, null_resettable) NSArray *inputRGBCompositeControlPoints; -@property (nonatomic,copy) NSNumber *inputIntensity; //default 1.0 +@property (nonatomic, copy, null_resettable) NSNumber *inputIntensity; //default 1.0 @end diff --git a/Sources/YUCIRGBToneCurve.m b/Sources/YUCIRGBToneCurve.m index 098df36..d346382 100644 --- a/Sources/YUCIRGBToneCurve.m +++ b/Sources/YUCIRGBToneCurve.m @@ -11,7 +11,7 @@ @interface YUCIRGBToneCurve () -@property (nonatomic,copy) NSArray *redCurve, *greenCurve, *blueCurve, *rgbCompositeCurve; +@property (nonatomic,copy) NSArray *redCurve, *greenCurve, *blueCurve, *rgbCompositeCurve; @property (nonatomic,strong) CIImage *toneCurveTexture; @@ -19,6 +19,11 @@ @interface YUCIRGBToneCurve () @implementation YUCIRGBToneCurve +@synthesize inputRGBCompositeControlPoints = _inputRGBCompositeControlPoints; +@synthesize inputRedControlPoints = _inputRedControlPoints; +@synthesize inputGreenControlPoints = _inputGreenControlPoints; +@synthesize inputBlueControlPoints = _inputBlueControlPoints; + + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ @@ -55,28 +60,17 @@ - (NSNumber *)inputIntensity { - (void)setDefaults { self.inputIntensity = nil; - self.redControlPoints = self.defaultCurveControlPoints; - self.greenControlPoints = self.defaultCurveControlPoints; - self.blueControlPoints = self.defaultCurveControlPoints; - self.rgbCompositeControlPoints = self.defaultCurveControlPoints; + self.inputRedControlPoints = self.defaultCurveControlPoints; + self.inputGreenControlPoints = self.defaultCurveControlPoints; + self.inputBlueControlPoints = self.defaultCurveControlPoints; + self.inputRGBCompositeControlPoints = self.defaultCurveControlPoints; } - (CIImage *)outputImage { - NSArray *defaultCurve = self.defaultCurveControlPoints; - if (self.redControlPoints.count == 0) { - self.redControlPoints = defaultCurve; - } - if (self.greenControlPoints.count == 0) { - self.greenControlPoints = defaultCurve; - } - if (self.blueControlPoints.count == 0) { - self.blueControlPoints = defaultCurve; - } - if (self.rgbCompositeControlPoints.count == 0) { - self.rgbCompositeControlPoints = defaultCurve; + if (!self.toneCurveTexture) { + [self validateInputs]; + [self updateToneCurveTexture]; } - [self updateToneCurveTexture]; - return [[YUCIRGBToneCurve filterKernel] applyWithExtent:self.inputImage.extent roiCallback:^CGRect(int index, CGRect destRect) { if (index == 0) { @@ -91,17 +85,21 @@ - (CIImage *)outputImage { } - (void)updateToneCurveTexture { - if (self.redCurve.count == 256 && self.greenCurve.count == 256 && self.blueCurve.count == 256 && self.rgbCompositeCurve.count == 256) { + if (self.redCurve.count == 256 && + self.greenCurve.count == 256 && + self.blueCurve.count == 256 && + self.rgbCompositeCurve.count == 256) + { uint8_t *toneCurveByteArray = calloc(256 * 4, sizeof(uint8_t)); for (unsigned int currentCurveIndex = 0; currentCurveIndex < 256; currentCurveIndex++) { // BGRA for upload to texture - uint8_t b = fmin(fmax(currentCurveIndex + [[self.blueCurve objectAtIndex:currentCurveIndex] floatValue], 0), 255); - toneCurveByteArray[currentCurveIndex * 4] = fmin(fmax(b + [[self.rgbCompositeCurve objectAtIndex:b] floatValue], 0), 255); - uint8_t g = fmin(fmax(currentCurveIndex + [[self.greenCurve objectAtIndex:currentCurveIndex] floatValue], 0), 255); - toneCurveByteArray[currentCurveIndex * 4 + 1] = fmin(fmax(g + [[self.rgbCompositeCurve objectAtIndex:g] floatValue], 0), 255); - uint8_t r = fmin(fmax(currentCurveIndex + [[self.redCurve objectAtIndex:currentCurveIndex] floatValue], 0), 255); - toneCurveByteArray[currentCurveIndex * 4 + 2] = fmin(fmax(r + [[self.rgbCompositeCurve objectAtIndex:r] floatValue], 0), 255); + uint8_t b = fmin(fmax(currentCurveIndex + self.blueCurve[currentCurveIndex].floatValue, 0), 255); + toneCurveByteArray[currentCurveIndex * 4] = fmin(fmax(b + self.rgbCompositeCurve[b].floatValue, 0), 255); + uint8_t g = fmin(fmax(currentCurveIndex + self.greenCurve[currentCurveIndex].floatValue, 0), 255); + toneCurveByteArray[currentCurveIndex * 4 + 1] = fmin(fmax(g + self.rgbCompositeCurve[g].floatValue, 0), 255); + uint8_t r = fmin(fmax(currentCurveIndex + self.redCurve[currentCurveIndex].floatValue, 0), 255); + toneCurveByteArray[currentCurveIndex * 4 + 2] = fmin(fmax(r + self.rgbCompositeCurve[r].floatValue, 0), 255); toneCurveByteArray[currentCurveIndex * 4 + 3] = 255; } CIImage *toneCurveTexture = [CIImage imageWithBitmapData:[NSData dataWithBytesNoCopy:toneCurveByteArray length:256 * 4 * sizeof(uint8_t) freeWhenDone:YES] bytesPerRow:256 * 4 * sizeof(uint8_t) size:CGSizeMake(256, 1) format:kCIFormatBGRA8 colorSpace:nil]; @@ -109,31 +107,87 @@ - (void)updateToneCurveTexture { } } -- (void)setRgbCompositeControlPoints:(NSArray *)newValue -{ - _rgbCompositeControlPoints = [newValue copy]; - _rgbCompositeCurve = [self getPreparedSplineCurve:_rgbCompositeControlPoints]; +- (void)validateInputs { + if (_inputRGBCompositeControlPoints.count == 0 || + _inputRedControlPoints.count == 0 || + _inputGreenControlPoints.count == 0 || + _inputBlueControlPoints.count == 0) + { + NSArray *defaultControlPoints = self.defaultCurveControlPoints; + NSArray *defaultCurve = [self getPreparedSplineCurve:defaultControlPoints]; + if (_inputRGBCompositeControlPoints.count == 0) { + _inputRGBCompositeControlPoints = defaultControlPoints.copy; + _rgbCompositeCurve = defaultCurve.copy; + } + if (_inputRedControlPoints.count == 0) { + _inputRedControlPoints = defaultControlPoints.copy; + _redCurve = defaultCurve.copy; + } + if (_inputGreenControlPoints.count == 0) { + _inputGreenControlPoints = defaultControlPoints.copy; + _greenCurve = defaultCurve.copy; + } + if (_inputBlueControlPoints.count == 0) { + _inputBlueControlPoints = defaultControlPoints.copy; + _blueCurve = defaultCurve.copy; + } + } } +- (NSArray *)inputRGBCompositeControlPoints { + [self validateInputs]; + return _inputRGBCompositeControlPoints; +} -- (void)setRedControlPoints:(NSArray *)newValue; -{ - _redControlPoints = [newValue copy]; - _redCurve = [self getPreparedSplineCurve:_redControlPoints]; +- (NSArray *)inputRedControlPoints { + [self validateInputs]; + return _inputRedControlPoints; } +- (NSArray *)inputGreenControlPoints { + [self validateInputs]; + return _inputGreenControlPoints; +} -- (void)setGreenControlPoints:(NSArray *)newValue -{ - _greenControlPoints = [newValue copy]; - _greenCurve = [self getPreparedSplineCurve:_greenControlPoints]; +- (NSArray *)inputBlueControlPoints { + [self validateInputs]; + return _inputBlueControlPoints; } +- (void)setInputRGBCompositeControlPoints:(NSArray *)inputRGBCompositeControlPoints { + if (![_inputRGBCompositeControlPoints isEqualToArray:inputRGBCompositeControlPoints]) { + _inputRGBCompositeControlPoints = inputRGBCompositeControlPoints.copy; + _rgbCompositeCurve = [self getPreparedSplineCurve:_inputRGBCompositeControlPoints]; + [self validateInputs]; + [self updateToneCurveTexture]; + } +} -- (void)setBlueControlPoints:(NSArray *)newValue -{ - _blueControlPoints = [newValue copy]; - _blueCurve = [self getPreparedSplineCurve:_blueControlPoints]; +- (void)setInputRedControlPoints:(NSArray *)inputRedControlPoints { + if (![_inputRedControlPoints isEqualToArray:inputRedControlPoints]) { + _inputRedControlPoints = inputRedControlPoints.copy; + _redCurve = [self getPreparedSplineCurve:_inputRedControlPoints]; + [self validateInputs]; + [self updateToneCurveTexture]; + } +} + +- (void)setInputGreenControlPoints:(NSArray *)inputGreenControlPoints { + if (![_inputGreenControlPoints isEqualToArray:inputGreenControlPoints]) { + _inputGreenControlPoints = inputGreenControlPoints.copy; + _greenCurve = [self getPreparedSplineCurve:_inputGreenControlPoints]; + [self validateInputs]; + [self updateToneCurveTexture]; + } +} + +- (void)setInputBlueControlPoints:(NSArray *)inputBlueControlPoints { + if (![_inputBlueControlPoints isEqualToArray:inputBlueControlPoints]) { + _inputBlueControlPoints = inputBlueControlPoints.copy; + _blueCurve = [self getPreparedSplineCurve:_inputBlueControlPoints]; + [self validateInputs]; + [self updateToneCurveTexture]; + } } #pragma mark - Curve calculation diff --git a/YUCIHighPassSkinSmoothingDemo/YUCIHighPassSkinSmoothingDemo/MetalRenderContextViewController.swift b/YUCIHighPassSkinSmoothingDemo/YUCIHighPassSkinSmoothingDemo/MetalRenderContextViewController.swift index 297ec6a..a16bcb2 100644 --- a/YUCIHighPassSkinSmoothingDemo/YUCIHighPassSkinSmoothingDemo/MetalRenderContextViewController.swift +++ b/YUCIHighPassSkinSmoothingDemo/YUCIHighPassSkinSmoothingDemo/MetalRenderContextViewController.swift @@ -35,7 +35,7 @@ class MetalRenderContextViewController: UIViewController, MTKViewDelegate { func drawInMTKView(view: MTKView) { let commandBuffer = self.commandQueue.commandBuffer() - let texture = YUCIMetalUtilities.textureFromCGImage(UIImage(named: "SampleImage")!.CGImage!, device: self.metalView.device) + let texture = YUCIMetalUtilities.textureFromCGImage(UIImage(named: "SampleImage")!.CGImage!, device: self.metalView.device!) let inputCIImage = CIImage(MTLTexture: texture, options: nil) let filter = CIFilter(name: "YUCIHighPassSkinSmoothing")! filter.setValue(inputCIImage, forKey: kCIInputImageKey)