From dc78bf9ac0c66f76223fa8560c051c525d9cb90a Mon Sep 17 00:00:00 2001 From: ruifanyuan Date: Mon, 25 Dec 2023 16:23:52 +0800 Subject: [PATCH] fix(ios): improve image rendering performance in main thread --- modules/ios/image/HippyDefaultImageProvider.m | 78 +++++++++++++++++-- .../ios/image/HippyImageProviderProtocol.h | 3 + modules/ios/image/NSData+DataType.m | 10 +-- .../component/image/HippyImageViewManager.mm | 15 +++- 4 files changed, 90 insertions(+), 16 deletions(-) diff --git a/modules/ios/image/HippyDefaultImageProvider.m b/modules/ios/image/HippyDefaultImageProvider.m index d870be66fd1..8242a3236ae 100644 --- a/modules/ios/image/HippyDefaultImageProvider.m +++ b/modules/ios/image/HippyDefaultImageProvider.m @@ -54,9 +54,70 @@ - (void)setImageData:(NSData *)imageData { } } +- (dispatch_queue_t)prepareQueue{ + return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); +} + +- (void)prepareForDisplay:(void (^)(UIImage *_Nullable))completionHandler{ + UIImage *theImage = [self image]; + BOOL fallback = YES; + + if(theImage){ + __weak typeof(self) weakSelf = self; + if (@available(iOS 15.0, *)) { + CFStringRef ut = CGImageGetUTType(theImage.CGImage); + // prepareForDisplayWithCompletionHandler support jpeg and heif only + if(ut != nil && (kCFCompareEqualTo == CFStringCompare(ut, kUTTypeJPEG, 0) || + kCFCompareEqualTo == CFStringCompare(ut, (__bridge CFStringRef)@"public.heif", 0))){ + fallback = NO; + + [theImage prepareForDisplayWithCompletionHandler:^(UIImage * _Nullable prepared) { + typeof(self) strongSelf = weakSelf; + if(strongSelf){ + @synchronized(strongSelf){ + if(prepared){ + strongSelf->_image = prepared; + } + completionHandler(prepared); + } + } + }]; + return; + } + } + + + dispatch_async([self prepareQueue], ^{ + UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] + initWithSize:theImage.size + format:[UIGraphicsImageRendererFormat preferredFormat]]; + UIImage *prepared = [renderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) { + [theImage drawAtPoint:CGPointZero]; + }]; + + typeof(self) strongSelf = weakSelf; + if(strongSelf){ + @synchronized(strongSelf){ + if(prepared){ + strongSelf->_image = prepared; + } + completionHandler(prepared); + } + } + }); + return; + } + + + if(fallback){ + completionHandler(nil); + } +} + - (UIImage *)image { CGFloat scale = [UIScreen mainScreen].scale; if (!_image) { + UIImage *tmp; if (_data) { CGFloat view_width = _imageViewSize.width; CGFloat view_height = _imageViewSize.height; @@ -82,7 +143,7 @@ - (UIImage *)image { (NSString *)kCGImageSourceThumbnailMaxPixelSize: @(maxDimensionInPixels) }; CGImageRef downsampleImageRef = CGImageSourceCreateThumbnailAtIndex(ref, 0, (__bridge CFDictionaryRef)downsampleOptions); - _image = [UIImage imageWithCGImage:downsampleImageRef scale:scale orientation:UIImageOrientationUp]; + tmp = [UIImage imageWithCGImage:downsampleImageRef scale:scale orientation:UIImageOrientationUp]; CGImageRelease(downsampleImageRef); } CFRelease(properties); @@ -91,13 +152,20 @@ - (UIImage *)image { } } } else { - _image = [self imageAtFrame:0]; + tmp = [self imageAtFrame:0]; + } + if(!tmp){ + tmp = [UIImage imageWithData:_data scale:scale]; + } + @synchronized (self) { + if(_image == nil){ + _image = tmp; + } } } - if (!_image) { - _image = [UIImage imageWithData:_data scale:scale]; + @synchronized (self) { + return _image; } - return _image; } - (UIImage *)imageAtFrame:(NSUInteger)index { diff --git a/modules/ios/image/HippyImageProviderProtocol.h b/modules/ios/image/HippyImageProviderProtocol.h index 60877dec7f4..9a7b86d3c7c 100644 --- a/modules/ios/image/HippyImageProviderProtocol.h +++ b/modules/ios/image/HippyImageProviderProtocol.h @@ -49,6 +49,9 @@ */ - (UIImage *)image; +/** prepare the image for display */ +- (void)prepareForDisplay:(void (^_Nullable)(UIImage *_Nullable))completionHandler; + // for animated Image @optional diff --git a/modules/ios/image/NSData+DataType.m b/modules/ios/image/NSData+DataType.m index 226240d1110..fefe3183dac 100644 --- a/modules/ios/image/NSData+DataType.m +++ b/modules/ios/image/NSData+DataType.m @@ -62,15 +62,7 @@ - (BOOL)datatype_isAPNG { } - (BOOL)datatype_isAnimatedImage { - do { - if ([self datatype_isGif]) { - return YES; - } - if ([self datatype_isAPNG]) { - return YES; - } - } while (0); - return NO; + return [self datatype_isGif] || [self datatype_isAPNG]; } @end diff --git a/renderer/native/ios/renderer/component/image/HippyImageViewManager.mm b/renderer/native/ios/renderer/component/image/HippyImageViewManager.mm index 69dc1227d1a..116fd0d2c71 100644 --- a/renderer/native/ios/renderer/component/image/HippyImageViewManager.mm +++ b/renderer/native/ios/renderer/component/image/HippyImageViewManager.mm @@ -83,13 +83,24 @@ - (void)loadImageSource:(NSString *)path forView:(HippyImageView *)view { HippyAssert(imageProvider, @"Image Provider is required"); imageProvider.imageDataPath = standardizeAssetUrlString; [imageProvider setImageData:data]; - dispatch_async(dispatch_get_main_queue(), ^{ + + void (^reloadImageInMain)(void) = ^{ HippyImageView *strongView = weakView; if (strongView) { [strongView setImageProvider:imageProvider]; [strongView reloadImage]; } - }); + }; + + if([imageProvider imageCount] <= 1){ + // prepare the still image for display before setting it to the imageview + [imageProvider prepareForDisplay:^(UIImage * _Nullable _) { + // subsequent call to the image provider will return the prepared image + dispatch_async(dispatch_get_main_queue(), reloadImageInMain); + }]; + }else{ + dispatch_async(dispatch_get_main_queue(), reloadImageInMain); + } } }); }