diff --git a/CONTRIBUTORS b/CONTRIBUTORS new file mode 100644 index 0000000..50fd607 --- /dev/null +++ b/CONTRIBUTORS @@ -0,0 +1 @@ +- Nam Kennic wrote the initial implementation for -setBackgroundColor:, on what was then the LFGlassView. diff --git a/LiveFrost.podspec b/LiveFrost.podspec index 580ff1c..e5a68cb 100644 --- a/LiveFrost.podspec +++ b/LiveFrost.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "LiveFrost" - s.version = "1.1.2" + s.version = "2.0" s.summary = "Real time blurring." s.homepage = "https://github.com/radi/LiveFrost" s.license = 'MIT' @@ -8,9 +8,9 @@ Pod::Spec.new do |s| "Evadne Wu" => "ev@radi.ws", "Nicholas Gabriel Levin" => "nl@radi.ws" } - s.source = { :git => "https://github.com/radi/LiveFrost.git", :tag => "1.1.2" } + s.source = { :git => "https://github.com/radi/LiveFrost.git", :tag => "2.0" } s.platform = :ios, '6.0' - s.source_files = 'LiveFrost', 'LiveFrost/**/*.{h,m}' + s.source_files = 'LiveFrost', 'LiveFrost/**/*.{h,m,c}' s.exclude_files = 'LiveFrost/Exclude' s.frameworks = 'Accelerate', 'QuartzCore', 'UIKit' s.requires_arc = true diff --git a/LiveFrost/LFDefines.c b/LiveFrost/LFDefines.c new file mode 100644 index 0000000..dffe44a --- /dev/null +++ b/LiveFrost/LFDefines.c @@ -0,0 +1,26 @@ +// +// Copyright (c) 2014 Evadne Wu and Nicholas Levin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#include "LFDefines.h" +#include + +const CGPoint LFPointNull = (CGPoint){INFINITY, INFINITY}; diff --git a/LiveFrost/LFDefines.h b/LiveFrost/LFDefines.h new file mode 100644 index 0000000..f9e2b21 --- /dev/null +++ b/LiveFrost/LFDefines.h @@ -0,0 +1,30 @@ +// +// Copyright (c) 2014 Evadne Wu and Nicholas Levin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#ifndef LiveFrost_LFDefines_h +#define LiveFrost_LFDefines_h + +#include + +extern const CGPoint LFPointNull; + +#endif diff --git a/LiveFrost/LFDisplayBridge.h b/LiveFrost/LFDisplayBridge.h index 788d767..1aa79b8 100644 --- a/LiveFrost/LFDisplayBridge.h +++ b/LiveFrost/LFDisplayBridge.h @@ -20,18 +20,14 @@ // THE SOFTWARE. // -#import - -@protocol LFDisplayBridgeTriggering -- (void) refresh; -@end +@import QuartzCore.CADisplayLink; +#import "LFDisplayBridgeTriggering.h" @interface LFDisplayBridge : NSObject + (instancetype) sharedInstance; -@property (nonatomic, readonly, assign) CFMutableSetRef subscribedViews; -- (void) addSubscribedViewsObject:(UIView *)object; -- (void) removeSubscribedViewsObject:(UIView *)object; +- (void) addSubscribedObject:(id)object; +- (void) removeSubscribedObject:(id)object; @end diff --git a/LiveFrost/LFDisplayBridge.m b/LiveFrost/LFDisplayBridge.m index 52599c0..fd1d6bc 100644 --- a/LiveFrost/LFDisplayBridge.m +++ b/LiveFrost/LFDisplayBridge.m @@ -22,24 +22,24 @@ #import "LFDisplayBridge.h" -void LF_refreshAllSubscribedViewsApplierFunction(const void *value, void *context); +void LF_refreshAllSubscribedObjectsApplierFunction(const void *value, void *context); @interface LFDisplayBridge () -@property (nonatomic, readwrite, assign) CFMutableSetRef subscribedViews; +@property (nonatomic, readwrite, assign) CFMutableSetRef subscribedObjects; @property (nonatomic, readonly, strong) CADisplayLink *displayLink; @end -void LF_refreshAllSubscribedViewsApplierFunction(const void *value, void *context) { - [(__bridge UIView *)value refresh]; +void LF_refreshAllSubscribedObjectsApplierFunction(const void *value, void *context) { + [(__bridge id)value refresh]; } #if !__has_feature(objc_arc) - #error This implementation file must be compiled with Objective-C ARC. +#error This implementation file must be compiled with Objective-C ARC. - #error Compile this file with the -fobjc-arc flag under your target's Build Phases, - #error or convert your project to Objective-C ARC. +#error Compile this file with the -fobjc-arc flag under your target's Build Phases, +#error or convert your project to Objective-C ARC. #endif @implementation LFDisplayBridge @@ -55,32 +55,32 @@ + (instancetype) sharedInstance { - (id) init { if (self = [super init]) { - _subscribedViews = CFSetCreateMutable(kCFAllocatorDefault, 0, NULL); + _subscribedObjects = CFSetCreateMutable(kCFAllocatorDefault, 0, NULL); _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)]; [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; } return self; } -- (void) addSubscribedViewsObject:(UIView *)object { - CFSetAddValue(_subscribedViews, (__bridge const void*)object); +- (void) dealloc { + [_displayLink invalidate]; + CFRelease(_subscribedObjects); } -- (void) removeSubscribedViewsObject:(UIView *)object { - CFSetRemoveValue(_subscribedViews, (__bridge const void*)object); +- (void) addSubscribedObject:(id)object { + CFSetAddValue(_subscribedObjects, (__bridge const void*)object); } -- (void) handleDisplayLink:(CADisplayLink *)displayLink { - [self refresh]; +- (void) removeSubscribedObject:(id)object { + CFSetRemoveValue(_subscribedObjects, (__bridge const void*)object); } -- (void) dealloc { - [_displayLink invalidate]; - CFRelease(_subscribedViews); +- (void) handleDisplayLink:(CADisplayLink *)displayLink { + [self refresh]; } - (void) refresh { - CFSetApplyFunction(_subscribedViews, LF_refreshAllSubscribedViewsApplierFunction, NULL); + CFSetApplyFunction(_subscribedObjects, LF_refreshAllSubscribedObjectsApplierFunction, NULL); } @end diff --git a/LiveFrost/LFDisplayBridgeTriggering.h b/LiveFrost/LFDisplayBridgeTriggering.h new file mode 100644 index 0000000..5fcd471 --- /dev/null +++ b/LiveFrost/LFDisplayBridgeTriggering.h @@ -0,0 +1,25 @@ +// +// Copyright (c) 2013-2014 Evadne Wu and Nicholas Levin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +@protocol LFDisplayBridgeTriggering +- (void) refresh; +@end diff --git a/LiveFrost/LFGlassLayer.h b/LiveFrost/LFGlassLayer.h new file mode 100644 index 0000000..99147a4 --- /dev/null +++ b/LiveFrost/LFGlassLayer.h @@ -0,0 +1,45 @@ +// +// Copyright (c) 2013-2014 Evadne Wu and Nicholas Levin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +@import Accelerate.vImage; +@import QuartzCore.CALayer; +#import "LFDisplayBridgeTriggering.h" + +@interface LFGlassLayer : CALayer + +@property (nonatomic, assign) CGFloat blurRadius; +@property (nonatomic, assign) CGFloat scaleFactor; + +@property (nonatomic, assign) NSUInteger frameInterval; + +- (BOOL) blurOnceIfPossible; + +// optional properties for greater customization +@property (nonatomic, weak) CALayer *customBlurTargetLayer; +@property (nonatomic, assign) CGRect customBlurBounds; +@property (nonatomic, assign) CGPoint customBlurPosition; +@property (nonatomic, assign) CGPoint customBlurAnchorPoint; +@property (nonatomic, assign) CGRect customBlurFrame; + +- (void) resetCustomPositioning; + +@end diff --git a/LiveFrost/LFGlassLayer.m b/LiveFrost/LFGlassLayer.m new file mode 100644 index 0000000..80c9bd4 --- /dev/null +++ b/LiveFrost/LFGlassLayer.m @@ -0,0 +1,416 @@ +// +// Copyright (c) 2013-2014 Evadne Wu and Nicholas Levin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#import "LFGlassLayer.h" +#import "LFDefines.h" + +@interface LFGlassLayer () + +@property (nonatomic, assign, readonly) CGSize cachedBufferSize; +@property (nonatomic, assign, readonly) CGSize scaledSize; + +@property (nonatomic, assign, readonly) CGContextRef effectInContext; +@property (nonatomic, assign, readonly) CGContextRef effectOutContext; + +@property (nonatomic, assign, readonly) vImage_Buffer effectInBuffer; +@property (nonatomic, assign, readonly) vImage_Buffer effectOutBuffer; + +@property (nonatomic, assign, readonly) uint32_t precalculatedBlurKernel; + +@property (nonatomic, assign, readonly) NSUInteger currentFrameInterval; + +@property (nonatomic, strong, readonly) CALayer *backgroundColorLayer; + +@property (nonatomic, assign, readonly) void *blurRadiusObserverContext; + +- (void) setup; +- (void) updatePrecalculatedBlurKernel; +- (void) adjustLayerAndImageBuffersFromFrame:(CGRect)fromFrame; +- (CGRect) visibleBoundsToBlur; +- (void) recalculateFrame; +- (CGRect) visibleFrameToBlur; +- (void) recreateImageBuffers; +- (void) forceRefresh; + +@end + +#if !__has_feature(objc_arc) +#error This implementation file must be compiled with Objective-C ARC. + +#error Compile this file with the -fobjc-arc flag under your target's Build Phases, +#error or convert your project to Objective-C ARC. +#endif + +void *LFGlassLayerBlurRadiusObserverContext = &LFGlassLayerBlurRadiusObserverContext; + +@implementation LFGlassLayer +@dynamic blurRadius; +@dynamic scaledSize; + +- (id) init { + if (self = [super init]) { + [self setup]; + } + return self; +} + +- (id) initWithLayer:(id)layer { + if (self = [super initWithLayer:layer]) { +#ifdef DEBUG + NSParameterAssert([layer isKindOfClass:[LFGlassLayer class]]); +#endif + LFGlassLayer *originalLayer = (LFGlassLayer*)layer; + [CATransaction begin]; + [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; + self.blurRadius = originalLayer.blurRadius; + [CATransaction commit]; + _scaleFactor = originalLayer.scaleFactor; + _frameInterval = originalLayer.frameInterval; + _customBlurTargetLayer = originalLayer.customBlurTargetLayer; + _customBlurBounds = originalLayer.customBlurBounds; + _customBlurPosition = originalLayer.customBlurPosition; + _customBlurAnchorPoint = originalLayer.customBlurAnchorPoint; + _customBlurFrame = originalLayer.customBlurFrame; + } + return self; +} + +- (id) initWithCoder:(NSCoder *)aDecoder { + if (self = [super initWithCoder:aDecoder]) { + [self setup]; + } + return self; +} + +- (void) setup { + [self addObserver:self + forKeyPath:@"blurRadius" + options:0 + context:LFGlassLayerBlurRadiusObserverContext]; + _blurRadiusObserverContext = LFGlassLayerBlurRadiusObserverContext; + + [CATransaction begin]; + [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; + self.blurRadius = 4.0f; + [CATransaction commit]; + + _backgroundColorLayer = [CALayer layer]; + _backgroundColorLayer.actions = @{ + @"bounds": [NSNull null], + @"position": [NSNull null] + }; + self.backgroundColor = [[UIColor clearColor] CGColor]; + self.scaleFactor = 0.25f; + self.opaque = NO; + self.actions = @{ + @"contents": [NSNull null], + @"hidden": [NSNull null] + }; + _frameInterval = 1; + _currentFrameInterval = 0; + [self resetCustomPositioning]; +} + +- (void) dealloc { + if (_blurRadiusObserverContext) { + [self removeObserver:self + forKeyPath:@"blurRadius" + context:LFGlassLayerBlurRadiusObserverContext]; + } + + if (_effectInContext) { + CGContextRelease(_effectInContext); + } + if (_effectOutContext) { + CGContextRelease(_effectOutContext); + } +} + +- (void) updatePrecalculatedBlurKernel { + uint32_t radius = (uint32_t)floor(self.blurRadius * 3. * sqrt(2 * M_PI) / 4 + 0.5); + radius += (radius + 1) % 2; + _precalculatedBlurKernel = radius; +} + +- (void) setBackgroundColor:(CGColorRef)backgroundColor { + [super setBackgroundColor:backgroundColor]; + + _backgroundColorLayer.backgroundColor = backgroundColor; + + if (CGColorGetAlpha(backgroundColor)) { + [self insertSublayer:self.backgroundColorLayer atIndex:0]; + } else { + [self.backgroundColorLayer removeFromSuperlayer]; + } +} + +- (void) setBounds:(CGRect)bounds { + CGRect oldFrame = [self visibleFrameToBlur]; + [super setBounds:bounds]; + [self adjustLayerAndImageBuffersFromFrame:oldFrame]; +} + +- (void) setPosition:(CGPoint)position { + CGRect oldFrame = [self visibleFrameToBlur]; + [super setPosition:position]; + [self adjustLayerAndImageBuffersFromFrame:oldFrame]; +} + +- (void) setAnchorPoint:(CGPoint)anchorPoint { + CGRect oldFrame = [self visibleFrameToBlur]; + [super setAnchorPoint:anchorPoint]; + [self adjustLayerAndImageBuffersFromFrame:oldFrame]; +} + +- (void) adjustLayerAndImageBuffersFromFrame:(CGRect)fromFrame { + if (CGRectEqualToRect(fromFrame, [self visibleFrameToBlur])) { + return; + } + + _backgroundColorLayer.frame = self.bounds; + + if (!CGRectIsEmpty(self.bounds)) { + [self recreateImageBuffers]; + } +} + +- (void) setScaleFactor:(CGFloat)scaleFactor { + _scaleFactor = scaleFactor; + CGSize scaledSize = self.scaledSize; + if (!CGSizeEqualToSize(_cachedBufferSize, scaledSize)) { + _cachedBufferSize = scaledSize; + [self recreateImageBuffers]; + } +} + +- (CGSize) scaledSize { + CGRect visibleBounds = [self visibleBoundsToBlur]; + CGSize scaledSize = (CGSize){ + _scaleFactor * CGRectGetWidth(visibleBounds), + _scaleFactor * CGRectGetHeight(visibleBounds) + }; + return scaledSize; +} + +- (void) setFrameInterval:(NSUInteger)frameInterval { + if (frameInterval == _frameInterval) { + return; + } + if (frameInterval == 0) { + NSLog(@"warning: attempted to set frameInterval to 0; frameInterval must be 1 or greater"); + return; + } + + _frameInterval = frameInterval; +} + +- (BOOL) blurOnceIfPossible { + if (!CGRectIsEmpty(self.bounds) && self.presentationLayer) { + [self forceRefresh]; + return YES; + } else { + return NO; + } +} + +- (void) setCustomBlurBounds:(CGRect)blurBounds { + _customBlurBounds = blurBounds; + [self recalculateFrame]; +} + +- (void) setCustomBlurAnchorPoint:(CGPoint)blurAnchorPoint { + _customBlurAnchorPoint = blurAnchorPoint; + [self recalculateFrame]; +} + +- (void) setCustomBlurPosition:(CGPoint)blurPosition { + _customBlurPosition = blurPosition; + [self recalculateFrame]; +} + +- (void) setCustomBlurFrame:(CGRect)blurFrame { + CGRect newBlurBounds = (CGRect){ CGPointZero, blurFrame.size }; + _customBlurBounds = newBlurBounds; + + _customBlurAnchorPoint = (CGPoint){ 0.5, 0.5 }; + + _customBlurPosition = (CGPoint){ + blurFrame.origin.x + 0.5 * CGRectGetWidth(newBlurBounds), + blurFrame.origin.y + 0.5 * CGRectGetHeight(newBlurBounds) + }; + + _customBlurFrame = blurFrame; +} + +- (void) resetCustomPositioning { + _customBlurBounds = CGRectNull; + _customBlurAnchorPoint = LFPointNull; + _customBlurPosition = LFPointNull; + _customBlurFrame = CGRectNull; +} + +- (CGRect) visibleBoundsToBlur { + return CGRectEqualToRect(_customBlurBounds, CGRectNull) ? self.bounds : _customBlurBounds; +} + +- (CGPoint) visibleAnchorPointToBlur { + return CGPointEqualToPoint(_customBlurAnchorPoint, LFPointNull) ? self.anchorPoint : _customBlurAnchorPoint; +} + +- (CGPoint) visiblePositionToBlur { + return CGPointEqualToPoint(_customBlurPosition, LFPointNull) ? self.position : _customBlurPosition; +} + +- (void) recalculateFrame { + CGRect bounds = [self visibleBoundsToBlur]; + CGPoint anchorPoint = [self visibleAnchorPointToBlur]; + CGPoint position = [self visiblePositionToBlur]; + + _customBlurFrame = (CGRect){ + -bounds.size.width * anchorPoint.x + position.x, + -bounds.size.height * anchorPoint.y + position.y, + bounds.size.width, + bounds.size.height + }; +} + +- (CGRect) visibleFrameToBlur { + return CGRectEqualToRect(_customBlurFrame, CGRectNull) ? self.frame : _customBlurFrame; +} + +- (void) recreateImageBuffers { + CGRect visibleRect = [self visibleFrameToBlur]; + CGSize bufferSize = self.scaledSize; + if (bufferSize.width == 0.0 || bufferSize.height == 0.0) { + return; + } + + size_t bufferWidth = (size_t)rint(bufferSize.width); + size_t bufferHeight = (size_t)rint(bufferSize.height); + if (bufferWidth == 0) { + bufferWidth = 1; + } + if (bufferHeight == 0) { + bufferHeight = 1; + } + + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + + CGContextRef effectInContext = CGBitmapContextCreate(NULL, bufferWidth, bufferHeight, 8, bufferWidth * 8, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); + + CGContextRef effectOutContext = CGBitmapContextCreate(NULL, bufferWidth, bufferHeight, 8, bufferWidth * 8, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); + + CGColorSpaceRelease(colorSpace); + + CGContextConcatCTM(effectInContext, (CGAffineTransform){ + 1.0, 0.0, 0.0, -1.0, 0.0, bufferSize.height + }); + CGContextScaleCTM(effectInContext, _scaleFactor, _scaleFactor); + CGContextTranslateCTM(effectInContext, -visibleRect.origin.x, -visibleRect.origin.y); + + if (_effectInContext) { + CGContextRelease(_effectInContext); + } + _effectInContext = effectInContext; + + if (_effectOutContext) { + CGContextRelease(_effectOutContext); + } + _effectOutContext = effectOutContext; + + _effectInBuffer = (vImage_Buffer){ + .data = CGBitmapContextGetData(effectInContext), + .width = CGBitmapContextGetWidth(effectInContext), + .height = CGBitmapContextGetHeight(effectInContext), + .rowBytes = CGBitmapContextGetBytesPerRow(effectInContext) + }; + + _effectOutBuffer = (vImage_Buffer){ + .data = CGBitmapContextGetData(effectOutContext), + .width = CGBitmapContextGetWidth(effectOutContext), + .height = CGBitmapContextGetHeight(effectOutContext), + .rowBytes = CGBitmapContextGetBytesPerRow(effectOutContext) + }; +} + +- (void) forceRefresh { + _currentFrameInterval = _frameInterval - 1; + [self refresh]; +} + +- (void) refresh { + if (++_currentFrameInterval < _frameInterval) { + return; + } + _currentFrameInterval = 0; + + CALayer *blurTargetLayer = _customBlurTargetLayer ? _customBlurTargetLayer : self.superlayer; + +#ifdef DEBUG + // generates a shadow copy + NSParameterAssert(self.presentationLayer); + NSParameterAssert(blurTargetLayer); + NSParameterAssert(_effectInContext); + NSParameterAssert(_effectOutContext); +#endif + + CGContextRef effectInContext = CGContextRetain(_effectInContext); + CGContextRef effectOutContext = CGContextRetain(_effectOutContext); + vImage_Buffer effectInBuffer = _effectInBuffer; + vImage_Buffer effectOutBuffer = _effectOutBuffer; + + self.hidden = YES; + [blurTargetLayer renderInContext:effectInContext]; + self.hidden = NO; + + uint32_t blurKernel = _precalculatedBlurKernel; + + vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, NULL, 0, 0, blurKernel, blurKernel, 0, kvImageEdgeExtend); + vImageBoxConvolve_ARGB8888(&effectOutBuffer, &effectInBuffer, NULL, 0, 0, blurKernel, blurKernel, 0, kvImageEdgeExtend); + vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, NULL, 0, 0, blurKernel, blurKernel, 0, kvImageEdgeExtend); + + CGImageRef outImage = CGBitmapContextCreateImage(effectOutContext); + self.contents = (__bridge id)(outImage); + CGImageRelease(outImage); + + CGContextRelease(effectInContext); + CGContextRelease(effectOutContext); +} + +- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if (context == LFGlassLayerBlurRadiusObserverContext) { + [self updatePrecalculatedBlurKernel]; + } else { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + } +} + ++ (id) defaultActionForKey:(NSString *)event { + if ([event isEqualToString:@"blurRadius"]) { + CATransition *blurAnimation = [CATransition animation]; + blurAnimation.type = kCATransitionFade; + return blurAnimation; + } + return [super defaultActionForKey:event]; +} + +@end diff --git a/LiveFrost/LFGlassView.h b/LiveFrost/LFGlassView.h index d8bd7eb..0ffa57f 100644 --- a/LiveFrost/LFGlassView.h +++ b/LiveFrost/LFGlassView.h @@ -19,15 +19,13 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // -// Contains contributions from Nam Kennic -// -#import -#import +@class LFGlassLayer; -@interface LFGlassView : UIView +@interface LFGlassView : UIView @property (nonatomic, assign) CGFloat blurRadius; +@property (nonatomic, assign) BOOL blurRadiusAnimationEnabled; @property (nonatomic, assign) CGFloat scaleFactor; @property (nonatomic, assign) NSUInteger frameInterval; @@ -36,4 +34,6 @@ - (BOOL) blurOnceIfPossible; +@property (nonatomic, weak, readonly) LFGlassLayer *glassLayer; + @end diff --git a/LiveFrost/LFGlassView.m b/LiveFrost/LFGlassView.m index 17d536d..1a5caa0 100644 --- a/LiveFrost/LFGlassView.m +++ b/LiveFrost/LFGlassView.m @@ -19,51 +19,40 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // -// Contains contributions from Nam Kennic -// #import "LFGlassView.h" +#import "LFGlassLayer.h" #import "LFDisplayBridge.h" -@interface LFGlassView () - -@property (nonatomic, assign, readonly) CGSize cachedBufferSize; -@property (nonatomic, assign, readonly) CGSize scaledSize; - -@property (nonatomic, assign, readonly) CGContextRef effectInContext; -@property (nonatomic, assign, readonly) CGContextRef effectOutContext; - -@property (nonatomic, assign, readonly) vImage_Buffer effectInBuffer; -@property (nonatomic, assign, readonly) vImage_Buffer effectOutBuffer; - -@property (nonatomic, assign, readonly) uint32_t precalculatedBlurKernel; +@interface LFGlassView () @property (nonatomic, assign, readonly) BOOL shouldLiveBlur; -@property (nonatomic, assign, readonly) NSUInteger currentFrameInterval; +@property (nonatomic, assign, readonly) BOOL blurOnceIfPossible; +@property (nonatomic, strong, readonly) NSSet *methodNamesToForwardToLayer; -@property (nonatomic, strong, readonly) CALayer *backgroundColorLayer; - -- (void) updatePrecalculatedBlurKernel; -- (void) adjustImageBuffersAndLayerFromFrame:(CGRect)fromFrame; -- (void) recreateImageBuffers; -- (void) startLiveBlurringIfReady; -- (void) stopLiveBlurring; -- (BOOL) isReadyToLiveBlur; -- (void) forceRefresh; +- (void) setup; +- (void) handleBlurringOnBoundsChange; @end #if !__has_feature(objc_arc) - #error This implementation file must be compiled with Objective-C ARC. +#error This implementation file must be compiled with Objective-C ARC. - #error Compile this file with the -fobjc-arc flag under your target's Build Phases, - #error or convert your project to Objective-C ARC. +#error Compile this file with the -fobjc-arc flag under your target's Build Phases, +#error or convert your project to Objective-C ARC. #endif @implementation LFGlassView -@dynamic scaledSize; +@dynamic blurRadius; +@dynamic scaleFactor; +@dynamic frameInterval; @dynamic liveBlurring; +@dynamic blurOnceIfPossible; + ++ (Class) layerClass { + return [LFGlassLayer class]; +} - (id) initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { @@ -80,110 +69,33 @@ - (id) initWithCoder:(NSCoder *)aDecoder { } - (void) setup { + _glassLayer = (LFGlassLayer*)self.layer; self.clipsToBounds = YES; - self.blurRadius = 4.0f; - _backgroundColorLayer = [CALayer layer]; - _backgroundColorLayer.actions = @{ - @"backgroundColor": [NSNull null], - @"bounds": [NSNull null], - @"position": [NSNull null] - }; - self.backgroundColor = [UIColor clearColor]; - self.scaleFactor = 0.25f; - self.opaque = NO; self.userInteractionEnabled = NO; - self.layer.actions = @{ - @"contents": [NSNull null] - }; _shouldLiveBlur = YES; - _frameInterval = 1; - _currentFrameInterval = 0; + _methodNamesToForwardToLayer = [NSSet setWithObjects:@"blurRadius", @"setBlurRadius:", @"scaleFactor", @"setScaleFactor:", @"frameInterval", @"setFrameInterval:", @"blurOnceIfPossible", nil]; } - (void) dealloc { - if (_effectInContext) { - CGContextRelease(_effectInContext); - } - if (_effectOutContext) { - CGContextRelease(_effectOutContext); - } [self stopLiveBlurring]; } -- (void) setBlurRadius:(CGFloat)blurRadius { - _blurRadius = blurRadius; - [self updatePrecalculatedBlurKernel]; -} - -- (void) updatePrecalculatedBlurKernel { - uint32_t radius = (uint32_t)floor(_blurRadius * 3. * sqrt(2 * M_PI) / 4 + 0.5); - radius += (radius + 1) % 2; - _precalculatedBlurKernel = radius; -} - -- (void) setScaleFactor:(CGFloat)scaleFactor { - _scaleFactor = scaleFactor; - CGSize scaledSize = self.scaledSize; - if (!CGSizeEqualToSize(_cachedBufferSize, scaledSize)) { - _cachedBufferSize = scaledSize; - [self recreateImageBuffers]; - } -} - -- (CGSize) scaledSize { - CGSize scaledSize = (CGSize){ - _scaleFactor * CGRectGetWidth(self.bounds), - _scaleFactor * CGRectGetHeight(self.bounds) - }; - return scaledSize; -} - -- (void) setFrame:(CGRect)frame { - CGRect fromFrame = self.frame; - [super setFrame:frame]; - [self adjustImageBuffersAndLayerFromFrame:fromFrame]; -} - - (void) setBounds:(CGRect)bounds { - CGRect fromFrame = self.frame; [super setBounds:bounds]; - [self adjustImageBuffersAndLayerFromFrame:fromFrame]; -} - -- (void) setCenter:(CGPoint)center { - CGRect fromFrame = self.frame; - [super setCenter:center]; - [self adjustImageBuffersAndLayerFromFrame:fromFrame]; + [self handleBlurringOnBoundsChange]; } -- (void) setBackgroundColor:(UIColor *)color { - [super setBackgroundColor:color]; - - CGColorRef backgroundCGColor = [color CGColor]; - - if (CGColorGetAlpha(backgroundCGColor)) { - _backgroundColorLayer.backgroundColor = backgroundCGColor; - [self.layer insertSublayer:_backgroundColorLayer atIndex:0]; - } else { - [_backgroundColorLayer removeFromSuperlayer]; - } +- (void) setFrame:(CGRect)frame { + [super setFrame:frame]; + [self handleBlurringOnBoundsChange]; } -- (void) adjustImageBuffersAndLayerFromFrame:(CGRect)fromFrame { - if (CGRectEqualToRect(fromFrame, self.frame)) { - return; - } - - _backgroundColorLayer.frame = self.bounds; - - if (!CGRectIsEmpty(self.bounds)) { - [self recreateImageBuffers]; - } else { +- (void) handleBlurringOnBoundsChange { + if (CGRectIsEmpty(self.bounds)) { [self stopLiveBlurring]; - return; + } else { + [self startLiveBlurringIfReady]; } - - [self startLiveBlurringIfReady]; } - (void) didMoveToSuperview { @@ -220,135 +132,39 @@ - (void) setLiveBlurring:(BOOL)liveBlurring { - (void) startLiveBlurringIfReady { if ([self isReadyToLiveBlur]) { - [self forceRefresh]; - [[LFDisplayBridge sharedInstance] addSubscribedViewsObject:self]; + [self blurOnceIfPossible]; + [[LFDisplayBridge sharedInstance] addSubscribedObject:_glassLayer]; } } - (void) stopLiveBlurring { - [[LFDisplayBridge sharedInstance] removeSubscribedViewsObject:self]; + [[LFDisplayBridge sharedInstance] removeSubscribedObject:_glassLayer]; } - (BOOL) isReadyToLiveBlur { return (!CGRectIsEmpty(self.bounds) && self.superview && self.window && _shouldLiveBlur); } -- (BOOL) blurOnceIfPossible { - if (!CGRectIsEmpty(self.bounds) && self.layer.presentationLayer) { - [self forceRefresh]; - return YES; - } else { - return NO; +- (NSMethodSignature *) methodSignatureForSelector:(SEL)aSelector { + if ([_methodNamesToForwardToLayer containsObject:NSStringFromSelector(aSelector)]) { + return [[self.glassLayer class] instanceMethodSignatureForSelector:aSelector]; } + return [super methodSignatureForSelector:aSelector]; } -- (void) setFrameInterval:(NSUInteger)frameInterval { - if (frameInterval == _frameInterval) { - return; - } - if (frameInterval == 0) { - NSLog(@"warning: attempted to set frameInterval to 0; frameInterval must be 1 or greater"); - return; - } - - _frameInterval = frameInterval; -} - -- (void) recreateImageBuffers { - CGRect visibleRect = self.frame; - CGSize bufferSize = self.scaledSize; - if (bufferSize.width == 0.0 || bufferSize.height == 0.0) { - return; - } - - size_t bufferWidth = (size_t)rint(bufferSize.width); - size_t bufferHeight = (size_t)rint(bufferSize.height); - if (bufferWidth == 0) { - bufferWidth = 1; - } - if (bufferHeight == 0) { - bufferHeight = 1; - } - - CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); - - CGContextRef effectInContext = CGBitmapContextCreate(NULL, bufferWidth, bufferHeight, 8, bufferWidth * 8, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); - - CGContextRef effectOutContext = CGBitmapContextCreate(NULL, bufferWidth, bufferHeight, 8, bufferWidth * 8, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); - - CGColorSpaceRelease(colorSpace); - - CGContextConcatCTM(effectInContext, (CGAffineTransform){ - 1.0, 0.0, 0.0, -1.0, 0.0, bufferSize.height - }); - CGContextScaleCTM(effectInContext, _scaleFactor, _scaleFactor); - CGContextTranslateCTM(effectInContext, -visibleRect.origin.x, -visibleRect.origin.y); - - if (_effectInContext) { - CGContextRelease(_effectInContext); - } - _effectInContext = effectInContext; - - if (_effectOutContext) { - CGContextRelease(_effectOutContext); +- (void) forwardInvocation:(NSInvocation *)anInvocation { + if ([self.glassLayer respondsToSelector:[anInvocation selector]]) { + [anInvocation invokeWithTarget:self.glassLayer]; + } else { + [super forwardInvocation:anInvocation]; } - _effectOutContext = effectOutContext; - - _effectInBuffer = (vImage_Buffer){ - .data = CGBitmapContextGetData(effectInContext), - .width = CGBitmapContextGetWidth(effectInContext), - .height = CGBitmapContextGetHeight(effectInContext), - .rowBytes = CGBitmapContextGetBytesPerRow(effectInContext) - }; - - _effectOutBuffer = (vImage_Buffer){ - .data = CGBitmapContextGetData(effectOutContext), - .width = CGBitmapContextGetWidth(effectOutContext), - .height = CGBitmapContextGetHeight(effectOutContext), - .rowBytes = CGBitmapContextGetBytesPerRow(effectOutContext) - }; -} - -- (void) forceRefresh { - _currentFrameInterval = _frameInterval - 1; - [self refresh]; } -- (void) refresh { - if (++_currentFrameInterval < _frameInterval) { - return; +- (id) actionForLayer:(CALayer *)layer forKey:(NSString *)event { + if ([event isEqualToString:@"blurRadius"] && self.blurRadiusAnimationEnabled) { + return nil; } - _currentFrameInterval = 0; - - UIView *superview = self.superview; -#ifdef DEBUG - NSParameterAssert(superview); - NSParameterAssert(self.window); - NSParameterAssert(_effectInContext); - NSParameterAssert(_effectOutContext); -#endif - - CGContextRef effectInContext = CGContextRetain(_effectInContext); - CGContextRef effectOutContext = CGContextRetain(_effectOutContext); - vImage_Buffer effectInBuffer = _effectInBuffer; - vImage_Buffer effectOutBuffer = _effectOutBuffer; - - self.hidden = YES; - [superview.layer renderInContext:effectInContext]; - self.hidden = NO; - - uint32_t blurKernel = _precalculatedBlurKernel; - - vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, NULL, 0, 0, blurKernel, blurKernel, 0, kvImageEdgeExtend); - vImageBoxConvolve_ARGB8888(&effectOutBuffer, &effectInBuffer, NULL, 0, 0, blurKernel, blurKernel, 0, kvImageEdgeExtend); - vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, NULL, 0, 0, blurKernel, blurKernel, 0, kvImageEdgeExtend); - - CGImageRef outImage = CGBitmapContextCreateImage(effectOutContext); - self.layer.contents = (__bridge id)(outImage); - CGImageRelease(outImage); - - CGContextRelease(effectInContext); - CGContextRelease(effectOutContext); + return [super actionForLayer:layer forKey:event]; } @end diff --git a/LiveFrost/LiveFrost.h b/LiveFrost/LiveFrost.h index 24e4e0f..923d602 100644 --- a/LiveFrost/LiveFrost.h +++ b/LiveFrost/LiveFrost.h @@ -20,5 +20,7 @@ // THE SOFTWARE. // -#import +#import #import +#import +#import