Add blur effect to RCTImageView

Summary: This diff introduces a blur radius property to the Image component on ios. If the radius specified is greater then 0 then native will apply a blur filter to the image

Reviewed By: nicklockwood

Differential Revision: D3054671

fb-gh-sync-id: d7a81ce5a08a3a2091c583f5053c6a86638b21b2
shipit-source-id: d7a81ce5a08a3a2091c583f5053c6a86638b21b2
This commit is contained in:
Reem Helou 2016-03-17 12:25:07 -07:00 committed by Facebook Github Bot 0
parent 8d7b419ed7
commit 9cb3ec9424
7 changed files with 122 additions and 2 deletions

View File

@ -94,6 +94,11 @@ var Image = React.createClass({
* @platform ios
*/
accessibilityLabel: PropTypes.string,
/**
* blurRadius: the blur radius of the blur filter added to the image
* @platform ios
*/
blurRadius: PropTypes.number,
/**
* When the image is resized, the corners of the size specified
* by capInsets will stay a fixed size, but the center content and borders
@ -206,7 +211,7 @@ var Image = React.createClass({
// This is a workaround for #8243665. RCTNetworkImageView does not support tintColor
// TODO: Remove this hack once we have one image implementation #8389274
if (isNetwork && tintColor) {
if (isNetwork && (tintColor || this.props.blurRadius)) {
RawImage = RCTImageView;
}

View File

@ -18,6 +18,7 @@
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879371AAD32A300F088A5 /* RCTImageLoader.m */; };
35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */; };
354631681B69857700AA0B86 /* RCTImageEditingManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 354631671B69857700AA0B86 /* RCTImageEditingManager.m */; };
EEF314721C9B0DD30049118E /* RCTImageBlurUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = EEF314711C9B0DD30049118E /* RCTImageBlurUtils.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@ -56,6 +57,8 @@
354631661B69857700AA0B86 /* RCTImageEditingManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageEditingManager.h; sourceTree = "<group>"; };
354631671B69857700AA0B86 /* RCTImageEditingManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageEditingManager.m; sourceTree = "<group>"; };
58B5115D1A9E6B3D00147676 /* libRCTImage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTImage.a; sourceTree = BUILT_PRODUCTS_DIR; };
EEF314701C9B0DD30049118E /* RCTImageBlurUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageBlurUtils.h; sourceTree = "<group>"; };
EEF314711C9B0DD30049118E /* RCTImageBlurUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageBlurUtils.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -72,6 +75,8 @@
58B511541A9E6B3D00147676 = {
isa = PBXGroup;
children = (
EEF314701C9B0DD30049118E /* RCTImageBlurUtils.h */,
EEF314711C9B0DD30049118E /* RCTImageBlurUtils.m */,
139A38821C4D57AD00862840 /* RCTResizeMode.h */,
139A38831C4D587C00862840 /* RCTResizeMode.m */,
13EF7F7D1BC825B1003F47DD /* RCTXCAssetImageLoader.h */,
@ -172,6 +177,7 @@
354631681B69857700AA0B86 /* RCTImageEditingManager.m in Sources */,
139A38841C4D587C00862840 /* RCTResizeMode.m in Sources */,
1304D5AB1AA8C4A30002E2BE /* RCTImageView.m in Sources */,
EEF314721C9B0DD30049118E /* RCTImageBlurUtils.m in Sources */,
13EF7F0B1BC42D4E003F47DD /* RCTShadowVirtualImage.m in Sources */,
13EF7F7F1BC825B1003F47DD /* RCTXCAssetImageLoader.m in Sources */,
134B00A21B54232B00EC8DFB /* RCTImageUtils.m in Sources */,

View File

@ -0,0 +1,16 @@
/*
* Copyright (c) 2013, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Accelerate/Accelerate.h>
#import <UIKit/UIKit.h>
#import "RCTDefines.h"
RCT_EXTERN UIImage *RCTBlurredImageWithRadius(UIImage *inputImage, CGFloat radius);

View File

@ -0,0 +1,77 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTImageBlurUtils.h"
UIImage *RCTBlurredImageWithRadius(UIImage *inputImage, CGFloat radius)
{
CGImageRef imageRef = inputImage.CGImage;
CGFloat imageScale = inputImage.scale;
UIImageOrientation imageOrientation = inputImage.imageOrientation;
// Image must be nonzero size
if (CGImageGetWidth(imageRef) * CGImageGetHeight(imageRef) == 0) {
return inputImage;
}
//convert to ARGB if it isn't
if (CGImageGetBitsPerPixel(imageRef) != 32 ||
CGImageGetBitsPerComponent(imageRef) != 8 ||
!((CGImageGetBitmapInfo(imageRef) & kCGBitmapAlphaInfoMask))) {
UIGraphicsBeginImageContextWithOptions(inputImage.size, NO, inputImage.scale);
[inputImage drawAtPoint:CGPointZero];
imageRef = UIGraphicsGetImageFromCurrentImageContext().CGImage;
UIGraphicsEndImageContext();
}
vImage_Buffer buffer1, buffer2;
buffer1.width = buffer2.width = CGImageGetWidth(imageRef);
buffer1.height = buffer2.height = CGImageGetHeight(imageRef);
buffer1.rowBytes = buffer2.rowBytes = CGImageGetBytesPerRow(imageRef);
size_t bytes = buffer1.rowBytes * buffer1.height;
buffer1.data = malloc(bytes);
buffer2.data = malloc(bytes);
// A description of how to compute the box kernel width from the Gaussian
// radius (aka standard deviation) appears in the SVG spec:
// http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement
uint32_t boxSize = floor((radius * imageScale * 3 * sqrt(2 * M_PI) / 4 + 0.5) / 2);
boxSize |= 1; // Ensure boxSize is odd
//create temp buffer
void *tempBuffer = malloc((size_t)vImageBoxConvolve_ARGB8888(&buffer1, &buffer2, NULL, 0, 0, boxSize, boxSize,
NULL, kvImageEdgeExtend + kvImageGetTempBufferSize));
//copy image data
CFDataRef dataSource = CGDataProviderCopyData(CGImageGetDataProvider(imageRef));
memcpy(buffer1.data, CFDataGetBytePtr(dataSource), bytes);
CFRelease(dataSource);
//perform blur
vImageBoxConvolve_ARGB8888(&buffer1, &buffer2, tempBuffer, 0, 0, boxSize, boxSize, NULL, kvImageEdgeExtend);
vImageBoxConvolve_ARGB8888(&buffer2, &buffer1, tempBuffer, 0, 0, boxSize, boxSize, NULL, kvImageEdgeExtend);
vImageBoxConvolve_ARGB8888(&buffer1, &buffer2, tempBuffer, 0, 0, boxSize, boxSize, NULL, kvImageEdgeExtend);
//free buffers
free(buffer2.data);
free(tempBuffer);
//create image context from buffer
CGContextRef ctx = CGBitmapContextCreate(buffer1.data, buffer1.width, buffer1.height,
8, buffer1.rowBytes, CGImageGetColorSpace(imageRef),
CGImageGetBitmapInfo(imageRef));
//create image from context
imageRef = CGBitmapContextCreateImage(ctx);
UIImage *outputImage = [UIImage imageWithCGImage:imageRef scale:imageScale orientation:imageOrientation];
CGImageRelease(imageRef);
CGContextRelease(ctx);
free(buffer1.data);
return outputImage;
}

View File

@ -22,5 +22,6 @@
@property (nonatomic, strong) UIImage *defaultImage;
@property (nonatomic, assign) UIImageRenderingMode renderingMode;
@property (nonatomic, strong) RCTImageSource *source;
@property (nonatomic, assign) CGFloat blurRadius;
@end

View File

@ -16,6 +16,7 @@
#import "RCTImageSource.h"
#import "RCTImageUtils.h"
#import "RCTUtils.h"
#import "RCTImageBlurUtils.h"
#import "UIView+React.h"
@ -100,6 +101,14 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
}
}
- (void)setBlurRadius:(CGFloat)blurRadius
{
if (blurRadius != _blurRadius) {
_blurRadius = blurRadius;
[self reloadImage];
}
}
- (void)setCapInsets:(UIEdgeInsets)capInsets
{
if (!UIEdgeInsetsEqualToEdgeInsets(_capInsets, capInsets)) {
@ -191,6 +200,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
}
RCTImageSource *source = _source;
CGFloat blurRadius = _blurRadius;
__weak RCTImageView *weakSelf = self;
_reloadImageCancellationBlock = [_bridge.imageLoader loadImageWithoutClipping:_source.imageURL.absoluteString
size:imageSize
@ -198,8 +208,12 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
resizeMode:(RCTResizeMode)self.contentMode
progressBlock:progressHandler
completionBlock:^(NSError *error, UIImage *image) {
RCTImageView *strongSelf = weakSelf;
if (blurRadius > __FLT_EPSILON__) {
// Do this on the background thread to avoid blocking interaction
image = RCTBlurredImageWithRadius(image, blurRadius);
}
dispatch_async(dispatch_get_main_queue(), ^{
RCTImageView *strongSelf = weakSelf;
if (![source isEqual:strongSelf.source]) {
// Bail out if source has changed since we started loading
return;

View File

@ -25,6 +25,7 @@ RCT_EXPORT_MODULE()
return [[RCTImageView alloc] initWithBridge:self.bridge];
}
RCT_EXPORT_VIEW_PROPERTY(blurRadius, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(capInsets, UIEdgeInsets)
RCT_REMAP_VIEW_PROPERTY(defaultSource, defaultImage, UIImage)
RCT_EXPORT_VIEW_PROPERTY(onLoadStart, RCTDirectEventBlock)