diff --git a/android/build.gradle b/android/build.gradle index 5b21cd59c..54949dd56 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -65,4 +65,6 @@ dependencies { implementation "com.github.bumptech.glide:glide:${glideVersion}" implementation "com.github.bumptech.glide:okhttp3-integration:${glideVersion}" annotationProcessor "com.github.bumptech.glide:compiler:${glideVersion}" + + implementation 'jp.wasabeef:glide-transformations:4.3.0' } diff --git a/android/src/main/java/com/dylanvann/fastimage/FastImageViewConverter.java b/android/src/main/java/com/dylanvann/fastimage/FastImageViewConverter.java index 86ca00d01..498c1ce34 100644 --- a/android/src/main/java/com/dylanvann/fastimage/FastImageViewConverter.java +++ b/android/src/main/java/com/dylanvann/fastimage/FastImageViewConverter.java @@ -25,8 +25,11 @@ import javax.annotation.Nullable; +import jp.wasabeef.glide.transformations.BlurTransformation; + class FastImageViewConverter { private static final Drawable TRANSPARENT_DRAWABLE = new ColorDrawable(Color.TRANSPARENT); + private static final int BLUR_SAMPLING = 3; private static final Map FAST_IMAGE_CACHE_CONTROL_MAP = new HashMap() {{ @@ -100,6 +103,8 @@ static RequestOptions getOptions(Context context, FastImageSource imageSource, R // Use defaults. break; } + // Get blur. + final int blurRadius = (int)FastImageViewConverter.getBlurRadius(source); RequestOptions options = new RequestOptions() .diskCacheStrategy(diskCacheStrategy) @@ -108,6 +113,10 @@ static RequestOptions getOptions(Context context, FastImageSource imageSource, R .priority(priority) .placeholder(TRANSPARENT_DRAWABLE); + if (blurRadius > 0) { + options = options.transform(new BlurTransformation((int)blurRadius, BLUR_SAMPLING)); + } + if (imageSource.isResource()) { // Every local resource (drawable) in Android has its own unique numeric id, which are // generated at build time. Although these ids are unique, they are not guaranteed unique @@ -121,6 +130,14 @@ static RequestOptions getOptions(Context context, FastImageSource imageSource, R return options; } + private static double getBlurRadius(ReadableMap source) { + if (source.hasKey("blurRadius")) { + return source.getDouble("blurRadius"); + } + + return 0; + } + private static FastImageCacheControl getCacheControl(ReadableMap source) { return getValueFromSource("cache", "immutable", FAST_IMAGE_CACHE_CONTROL_MAP, source); } diff --git a/ios/FastImage/FFFastImageView.h b/ios/FastImage/FFFastImageView.h index e52fca798..c5b0bae3a 100644 --- a/ios/FastImage/FFFastImageView.h +++ b/ios/FastImage/FFFastImageView.h @@ -19,6 +19,7 @@ @property (nonatomic, strong) FFFastImageSource *source; @property (nonatomic, strong) UIImage *defaultSource; @property (nonatomic, strong) UIColor *imageColor; +@property (nonatomic, assign) CGFloat blurRadius; @end diff --git a/ios/FastImage/FFFastImageView.m b/ios/FastImage/FFFastImageView.m index f7100815e..17d95eb07 100644 --- a/ios/FastImage/FFFastImageView.m +++ b/ios/FastImage/FFFastImageView.m @@ -1,6 +1,7 @@ #import "FFFastImageView.h" #import #import +#import @interface FFFastImageView () @@ -71,6 +72,13 @@ - (void) setImageColor: (UIColor*)imageColor { } } +- (void)setBlurRadius:(CGFloat)blurRadius { + if (_blurRadius != blurRadius) { + _blurRadius = blurRadius; + _needsReload = YES; + } +} + - (UIImage*) makeImage: (UIImage*)image withTint: (UIColor*)color { UIImage* newImage = [image imageWithRenderingMode: UIImageRenderingModeAlwaysTemplate]; UIGraphicsBeginImageContextWithOptions(image.size, NO, newImage.scale); @@ -82,6 +90,13 @@ - (UIImage*) makeImage: (UIImage*)image withTint: (UIColor*)color { } - (void) setImage: (UIImage*)image { + if (_blurRadius && _blurRadius > 0) { + UIImage *blurImage = [self blurImage: image withRadius: _blurRadius]; + if (blurImage) { + image = blurImage; + } + } + if (self.imageColor != nil) { super.image = [self makeImage: image withTint: self.imageColor]; } else { @@ -237,6 +252,29 @@ - (void) downloadImage: (FFFastImageSource*)source options: (SDWebImageOptions)o }]; } +- (UIImage *)blurImage:(UIImage *)image withRadius:(CGFloat)radius { + CIContext *context = [CIContext contextWithOptions:nil]; + CIImage *inputImage = [CIImage imageWithCGImage:image.CGImage]; + + CIFilter *filter = [CIFilter filterWithName:@"CIGaussianBlur"]; + [filter setValue:inputImage forKey:kCIInputImageKey]; + [filter setValue:[NSNumber numberWithFloat:radius] forKey:kCIInputRadiusKey]; + CIImage *outputImage = [filter valueForKey:kCIOutputImageKey]; + + if (outputImage) { + CGRect rect = CGRectMake(radius * 2, radius * 2, image.size.width - radius * 4, image.size.height - radius * 4); + CGImageRef outputImageRef = [context createCGImage:outputImage fromRect:rect]; + + if (outputImageRef) { + UIImage *blurImage = [UIImage imageWithCGImage:outputImageRef]; + CGImageRelease(outputImageRef); + return blurImage; + } + } + + return nil; +} + - (void) dealloc { [self sd_cancelCurrentImageLoad]; } diff --git a/ios/FastImage/FFFastImageViewManager.m b/ios/FastImage/FFFastImageViewManager.m index 84ca94e26..76376e627 100644 --- a/ios/FastImage/FFFastImageViewManager.m +++ b/ios/FastImage/FFFastImageViewManager.m @@ -21,6 +21,7 @@ - (FFFastImageView*)view { RCT_EXPORT_VIEW_PROPERTY(onFastImageLoad, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onFastImageLoadEnd, RCTDirectEventBlock) RCT_REMAP_VIEW_PROPERTY(tintColor, imageColor, UIColor) +RCT_EXPORT_VIEW_PROPERTY(blurRadius, CGFloat) RCT_EXPORT_METHOD(preload:(nonnull NSArray *)sources) { diff --git a/src/index.js.flow b/src/index.js.flow index 8124c868a..b9a26ef76 100644 --- a/src/index.js.flow +++ b/src/index.js.flow @@ -60,6 +60,7 @@ export type FastImageProps = $ReadOnly<{| defaultSource?: ?number, tintColor?: number | string, + blurRadius?: number, resizeMode?: ?ResizeModes, fallback?: ?boolean, testID?: ?string, diff --git a/src/index.tsx b/src/index.tsx index 478c629e9..9d5ebd566 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -50,6 +50,7 @@ export type Source = { headers?: { [key: string]: string } priority?: Priority cache?: Cache + blurRadius?: number } export interface OnLoadEvent { @@ -119,6 +120,13 @@ export interface FastImageProps extends AccessibilityProps, ViewProps { tintColor?: ColorValue + /** + * BlurRadius + * + * The blur radius of the blur filter added to the image. + */ + blurRadius?: number + /** * A unique identifier for this element to be used in UI Automation testing scripts. */ @@ -132,7 +140,7 @@ export interface FastImageProps extends AccessibilityProps, ViewProps { const resolveDefaultSource = ( defaultSource?: ImageRequireSource, -): string | number | null => { +): string | number | null | undefined => { if (!defaultSource) { return null } @@ -143,7 +151,7 @@ const resolveDefaultSource = ( ) if (resolved) { - return resolved.uri + return resolved?.uri } return null @@ -157,6 +165,7 @@ function FastImageBase({ source, defaultSource, tintColor, + blurRadius, onLoadStart, onProgress, onLoad, @@ -188,6 +197,7 @@ function FastImageBase({ onError={onError} onLoadEnd={onLoadEnd} resizeMode={resizeMode} + blurRadius={blurRadius} /> {children} @@ -197,13 +207,18 @@ function FastImageBase({ const resolvedSource = Image.resolveAssetSource(source as any) const resolvedDefaultSource = resolveDefaultSource(defaultSource) + const resultSource = Platform.OS === 'android' + ? Object.assign({}, resolvedSource, { blurRadius: blurRadius }) + : resolvedSource + return ( = forwardRef( FastImageComponent.displayName = 'FastImage' export interface FastImageStaticProperties { + blurRadius: number, resizeMode: typeof resizeMode priority: typeof priority cacheControl: typeof cacheControl