Created
October 1, 2013 07:54
-
-
Save robertjpayne/6775157 to your computer and use it in GitHub Desktop.
Speedup ios-realtimeblur
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// DRNBoxBlur.h | |
// LiveBlur | |
// | |
// Created by Robert Payne on 1/10/13. | |
// Copyright (c) 2013 Zwopple Limited. All rights reserved. | |
// | |
#import <Foundation/Foundation.h> | |
@interface DRNBoxBlur : NSObject | |
+ (UIImage *)imageByBlurringRBA8888PixelData:(void *)pixelData width:(int64_t)width height:(int64_t)height amount:(CGFloat)amount; | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// DRNBoxBlur.m | |
// LiveBlur | |
// | |
// Created by Robert Payne on 1/10/13. | |
// Copyright (c) 2013 Zwopple Limited. All rights reserved. | |
// | |
#import "DRNBoxBlur.h" | |
#import <Accelerate/Accelerate.h> | |
@implementation DRNBoxBlur | |
+ (UIImage *)imageByBlurringRBA8888PixelData:(void *)pixelData width:(int64_t)width height:(int64_t)height amount:(CGFloat)amount { | |
if(width == 0 || height == 0) { | |
return nil; | |
} | |
int boxSize = (int)(amount * 40); | |
boxSize = boxSize - (boxSize % 2) + 1; | |
vImage_Buffer vBuffer; | |
vBuffer.width = width; | |
vBuffer.height = height; | |
vBuffer.data = pixelData; | |
vBuffer.rowBytes = sizeof(unsigned int) * width; | |
vImage_Error error; | |
error = vImageBoxConvolve_ARGB8888(&vBuffer, &vBuffer, NULL, 0, 0, boxSize, boxSize, NULL, kvImageEdgeExtend) | |
? : vImageBoxConvolve_ARGB8888(&vBuffer, &vBuffer, NULL, 0, 0, boxSize, boxSize, NULL, kvImageEdgeExtend) | |
? : vImageBoxConvolve_ARGB8888(&vBuffer, &vBuffer, NULL, 0, 0, boxSize, boxSize, NULL, kvImageEdgeExtend); | |
if(error) { | |
return nil; | |
} | |
NSData *data = [NSData dataWithBytesNoCopy:vBuffer.data length:vBuffer.rowBytes * height freeWhenDone:NO]; | |
CGDataProviderRef dp = CGDataProviderCreateWithCFData((__bridge CFDataRef)data); | |
CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB(); | |
CGImageRef img = CGImageCreate(width, height, 8, 32, vBuffer.rowBytes, cs, (CGBitmapInfo)kCGImageAlphaPremultipliedLast, dp, NULL, FALSE, kCGRenderingIntentDefault); | |
CGColorSpaceRelease(cs); | |
CGDataProviderRelease(dp); | |
data = nil; | |
UIImage *image = [UIImage imageWithCGImage:img scale:[[UIScreen mainScreen] scale] orientation:UIImageOrientationDown]; | |
CGImageRelease(img); | |
return image; | |
} | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) 2013 Alex Usbergo | |
// | |
// 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. | |
// | |
// | |
// DRNRealTimeBlurView.m | |
// LiveBlurView | |
// | |
// Created by Alex Usbergo on 7/3/13. | |
// Copyright (c) 2013 Alex Usbergo. All rights reserved. | |
// | |
#import "DRNRealTimeBlurView.h" | |
#import "DRNBoxBlur.h" | |
#import "UIImage+BoxBlur.h" | |
#import <QuartzCore/QuartzCore.h> | |
/* In order to optimize the number of calls to renderLayerWithView in DRNRealTimeBlurView | |
* this centralized manager register and deregister the views and makes sure that they're | |
* refreshed when is needed */ | |
@interface DRNRealTimeBlurViewManager : NSObject | |
@property (nonatomic, strong) NSMutableArray *views; | |
/* Returns the global instance */ | |
+ (id)sharedManager; | |
/* Register and deregister views */ | |
- (void)registerView:(DRNRealTimeBlurView*)view; | |
- (void)deregisterView:(DRNRealTimeBlurView*)view; | |
@end | |
@interface DRNRealTimeBlurView () | |
/* The background layer with the tint color */ | |
@property (nonatomic, strong) CALayer *tintLayer; | |
/* flags to determine if properties were set in nib */ | |
@property (nonatomic, assign) BOOL didSetCornerRadius; | |
@property (nonatomic, assign) BOOL didSetClipsToBounds; | |
@property (nonatomic, assign) BOOL didSetBlurRadius; | |
/* Private layer rendering method */ | |
- (void)renderLayerWithView:(UIView*)superview; | |
@end | |
@implementation DRNRealTimeBlurView | |
#pragma mark - Constructors | |
- (id)initWithFrame:(CGRect)frame | |
{ | |
if (self = [super initWithFrame:frame]) { | |
[self setup]; | |
} | |
return self; | |
} | |
- (id)initWithCoder:(NSCoder *)aDecoder | |
{ | |
if (self = [super initWithCoder:aDecoder]) { | |
[self setup]; | |
} | |
return self; | |
} | |
- (void)setup | |
{ | |
// Initialization code | |
self.tintLayer = [[CALayer alloc] init]; | |
self.tintLayer.frame = self.bounds; | |
self.tintLayer.opacity = kDNRRealTimeBlurTintColorAlpha; | |
//default tint (use tintColor on iOS7) | |
if ([self respondsToSelector:@selector(tintColor)]) | |
self.tint = [self valueForKey:@"tintColor"] ?: self.tint; | |
else | |
self.tint = [UIColor clearColor]; | |
[self.layer addSublayer:self.tintLayer]; | |
//don't override values set in nib | |
if (!_didSetClipsToBounds) self.clipsToBounds = YES; | |
if (!_didSetCornerRadius) self.layer.cornerRadius = kDRNRealTimeBlurViewDefaultCornerRadius; | |
if (!_didSetBlurRadius) self.blurRadius = kDRNRealTimeBlurViewBlurRadius; | |
} | |
- (void)dealloc | |
{ | |
[[DRNRealTimeBlurViewManager sharedManager] deregisterView:self]; | |
} | |
#pragma mark - Properties | |
/* When renderStatic is YES, the view is not rendered every kDRNRealTimeBlurViewRenderPeriod seconds. | |
* Useful when the view is presented modally and there's no interaction in the superview. | |
* IMPORTANT: Always set the view as static before animating this view (you | |
* can always set it back to non-static by calling setRenderStatic:NO in the completion block) */ | |
- (void)setRenderStatic:(BOOL)renderStatic | |
{ | |
_renderStatic = renderStatic; | |
if (renderStatic) | |
[[DRNRealTimeBlurViewManager sharedManager] deregisterView:self]; | |
else | |
[[DRNRealTimeBlurViewManager sharedManager] registerView:self]; | |
} | |
/* force the view to refresh */ | |
- (void)refresh | |
{ | |
[[DRNRealTimeBlurViewManager sharedManager] registerView:self]; | |
if (_renderStatic) | |
{ | |
[[DRNRealTimeBlurViewManager sharedManager] deregisterView:self]; | |
} | |
} | |
/* Set the tint color of the view. (default = [UIColor clearColor]) */ | |
- (void)setTint:(UIColor*)tint | |
{ | |
_tint = tint; | |
self.tintLayer.backgroundColor = _tint.CGColor; | |
[self.tintLayer setNeedsDisplay]; | |
} | |
- (void)setBlurRadius:(CGFloat)blurRadius | |
{ | |
if (_blurRadius != blurRadius) { | |
_blurRadius = blurRadius; | |
[self refresh]; | |
} | |
} | |
- (void)setClipsToBounds:(BOOL)clipsToBounds | |
{ | |
super.clipsToBounds = clipsToBounds; | |
_didSetClipsToBounds = YES; | |
} | |
- (void)setValue:(id)value forKeyPath:(NSString*)keyPath | |
{ | |
[super setValue:value forKeyPath:keyPath]; | |
if ([keyPath isEqualToString:@"layer.cornerRadius"]) { | |
_didSetCornerRadius = YES; | |
} | |
} | |
#pragma mark - Rendering | |
/* When the view is pushed to the superview, the layer | |
* is rendered for the first time. It will then be refreshed | |
* only if renderStatic = YES */ | |
- (void)willMoveToSuperview:(UIView*)superview | |
{ | |
[self renderLayerWithView:superview]; | |
} | |
/* When the view is visible because is being added to the superview | |
* or to the window, this method is called */ | |
- (void)didMoveToSuperview | |
{ | |
if (nil != self.superview) { | |
//if the view is not set as static registers the view to the manager | |
if (!self.renderStatic) | |
[[DRNRealTimeBlurViewManager sharedManager] registerView:self]; | |
} else { | |
//makes sure that the view is deregistered from the manager | |
[[DRNRealTimeBlurViewManager sharedManager] deregisterView:self]; | |
} | |
} | |
- (void)renderLayerWithView:(UIView*)superview | |
{ | |
//get the visible rect | |
CGRect visibleRect = [superview convertRect:self.frame toView:self]; | |
visibleRect.origin.y += self.frame.origin.y; | |
visibleRect.origin.x += self.frame.origin.x; | |
if(visibleRect.size.width <= 0 || visibleRect.size.height <= 0) { | |
return; | |
} | |
//hide all the blurred views from the superview before taking a screenshot | |
CGFloat alpha = self.alpha; | |
[self toggleBlurViewsInView:superview hidden:YES alpha:alpha]; | |
//Render the layer in the image context | |
//Render the layer in the image context | |
void *pixelData = malloc(sizeof(unsigned int) * visibleRect.size.width * visibleRect.size.height); | |
CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB(); | |
CGContextRef ctx = CGBitmapContextCreate(pixelData, visibleRect.size.width, visibleRect.size.height, 8, visibleRect.size.width * sizeof(unsigned int), cs, (CGBitmapInfo)kCGImageAlphaPremultipliedLast); | |
CGColorSpaceRelease(cs); | |
CGContextClearRect(ctx, CGRectMake(0.0, 0.0, visibleRect.size.width, visibleRect.size.height)); | |
CGContextTranslateCTM(ctx, -visibleRect.origin.x, -visibleRect.origin.y); | |
CALayer *layer = superview.layer; | |
[layer renderInContext:ctx]; | |
//show all the blurred views from the superview before taking a screenshot | |
[self toggleBlurViewsInView:superview hidden:NO alpha:alpha]; | |
CGContextRelease(ctx); | |
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ | |
UIImage *image = [DRNBoxBlur imageByBlurringRBA8888PixelData:pixelData | |
width:visibleRect.size.width | |
height:visibleRect.size.height | |
amount:_blurRadius]; | |
free(pixelData); | |
dispatch_sync(dispatch_get_main_queue(), ^{ | |
//update the layer content | |
self.layer.contents = (id)image.CGImage; | |
}); | |
}); | |
} | |
/* Hide or show all the DRNRealTimeBlurView subviews from the target view */ | |
- (void)toggleBlurViewsInView:(UIView*)view hidden:(BOOL)hidden alpha:(CGFloat)originalAlpha | |
{ | |
for (UIView *subview in view.subviews) | |
if ([subview isKindOfClass:DRNRealTimeBlurView.class]) | |
subview.alpha = hidden ? 0.f : originalAlpha; | |
} | |
@end | |
#pragma mark - DRNRealTimeBlurViewManager implementation | |
/* In order to optimize the number of calls to renderLayerWithView in DRNRealTimeBlurView | |
* this centralized manager register and deregister the views and makes sure that they're | |
* refreshed when is needed */ | |
@implementation DRNRealTimeBlurViewManager | |
/* Returns the global instance */ | |
+ (id)sharedManager | |
{ | |
static dispatch_once_t pred; | |
static DRNRealTimeBlurViewManager *shared = nil; | |
dispatch_once(&pred, ^{ | |
shared = [[DRNRealTimeBlurViewManager alloc] init]; | |
}); | |
return shared; | |
} | |
/* Constructor */ | |
- (id)init | |
{ | |
if (self = [super init]) | |
self.views = [@[] mutableCopy]; | |
return self; | |
} | |
/* Register a new view and starts the pulling */ | |
- (void)registerView:(DRNRealTimeBlurView*)view | |
{ | |
//add the view to the collection (if it's not already included) | |
if (![self.views containsObject:view]) [self.views addObject:view]; | |
[self refresh]; | |
} | |
/* Deregister the view */ | |
- (void)deregisterView:(DRNRealTimeBlurView*)view | |
{ | |
[self.views removeObject:view]; | |
[self refresh]; | |
} | |
/* Pull the new screenshot data from the superviews and forces | |
* the registered views to render */ | |
- (void)refresh | |
{ | |
if (!self.views.count) return; | |
//refresh all the registered views | |
for (DRNRealTimeBlurView *view in self.views) | |
[view renderLayerWithView:view.superview]; | |
double delayInSeconds = self.views.count * (1/kDRNRealTimeBlurViewRenderFps); | |
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); | |
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ | |
[self refresh]; | |
}); | |
} | |
@end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
There's a typo : DNRBoxBlur.h instead of DRNBoxBlur.h