Skip to content

Commit

Permalink
Merge pull request #3559 from dreampiggy/performance/default_bitmap_a…
Browse files Browse the repository at this point in the history
…nd_force_decode

Refactory the logic to handle force decode logic to avoid CA copy frame buffer, introduce SDImageForceDecodePolicy detailed control
  • Loading branch information
dreampiggy committed Jul 13, 2023
2 parents f5f27a9 + 808cedc commit 8f16a63
Show file tree
Hide file tree
Showing 20 changed files with 449 additions and 101 deletions.
4 changes: 3 additions & 1 deletion Examples/SDWebImage Demo/DetailViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ - (void)configureView {
}
[self.imageView sd_setImageWithURL:self.imageURL
placeholderImage:nil
options:SDWebImageProgressiveLoad | SDWebImageScaleDownLargeImages];
options:SDWebImageProgressiveLoad | SDWebImageScaleDownLargeImages
context:@{SDWebImageContextImageForceDecodePolicy: @(SDImageForceDecodePolicyNever)}
];
self.imageView.shouldCustomLoopCount = YES;
self.imageView.animationRepeatCount = 0;
}
Expand Down
4 changes: 3 additions & 1 deletion SDWebImage/Core/SDImageCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,10 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
/**
* By default, we will decode the image in the background during cache query and download from the network. This can help to improve performance because when rendering image on the screen, it need to be firstly decoded. But this happen on the main queue by Core Animation.
* However, this process may increase the memory usage as well. If you are experiencing a issue due to excessive memory consumption, This flag can prevent decode the image.
* @note 5.14.0 introduce `SDImageCoderDecodeUseLazyDecoding`, use that for better control from codec, instead of post-processing. Which acts the similar like this option but works for SDAnimatedImage as well (this one does not)
* @deprecated Deprecated in v5.17.0, if you don't want force-decode, pass [.imageForceDecodePolicy] = [SDImageForceDecodePolicy.never] in context option
*/
SDImageCacheAvoidDecodeImage = 1 << 4,
SDImageCacheAvoidDecodeImage API_DEPRECATED("Use SDWebImageContextImageForceDecodePolicy instead", macos(10.10, 10.10), ios(8.0, 8.0), tvos(9.0, 9.0), watchos(2.0, 2.0)) = 1 << 4,
/**
* By default, we decode the animated image. This flag can force decode the first frame only and produce the static image.
*/
Expand Down
6 changes: 6 additions & 0 deletions SDWebImage/Core/SDImageCache.m
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,8 @@ - (void)calculateSizeWithCompletionBlock:(nullable SDImageCacheCalculateSizeBloc
}

#pragma mark - Helper
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ (SDWebImageOptions)imageOptionsFromCacheOptions:(SDImageCacheOptions)cacheOptions {
SDWebImageOptions options = 0;
if (cacheOptions & SDImageCacheScaleDownLargeImages) options |= SDWebImageScaleDownLargeImages;
Expand All @@ -893,6 +895,7 @@ + (SDWebImageOptions)imageOptionsFromCacheOptions:(SDImageCacheOptions)cacheOpti

return options;
}
#pragma clang diagnostic pop

@end

Expand All @@ -904,6 +907,8 @@ @implementation SDImageCache (SDImageCache)
return [self queryImageForKey:key options:options context:context cacheType:SDImageCacheTypeAll completion:completionBlock];
}

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- (id<SDWebImageOperation>)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock {
SDImageCacheOptions cacheOptions = 0;
if (options & SDWebImageQueryMemoryData) cacheOptions |= SDImageCacheQueryMemoryData;
Expand All @@ -917,6 +922,7 @@ @implementation SDImageCache (SDImageCache)

return [self queryCacheOperationForKey:key options:cacheOptions context:context cacheType:cacheType done:completionBlock];
}
#pragma clang diagnostic pop

- (void)storeImage:(UIImage *)image imageData:(NSData *)imageData forKey:(nullable NSString *)key cacheType:(SDImageCacheType)cacheType completion:(nullable SDWebImageNoParamsBlock)completionBlock {
[self storeImage:image imageData:imageData forKey:key options:0 context:nil cacheType:cacheType completion:completionBlock];
Expand Down
18 changes: 11 additions & 7 deletions SDWebImage/Core/SDImageCacheDefine.m
Original file line number Diff line number Diff line change
Expand Up @@ -123,15 +123,19 @@ void SDSetDecodeOptionsToContext(SDWebImageMutableContext * _Nonnull mutableCont
image = [imageCoder decodedImageWithData:imageData options:coderOptions];
}
if (image) {
BOOL shouldDecode = !SD_OPTIONS_CONTAINS(options, SDWebImageAvoidDecodeImage);
BOOL lazyDecode = [coderOptions[SDImageCoderDecodeUseLazyDecoding] boolValue];
if (lazyDecode) {
// lazyDecode = NO means we should not forceDecode, highest priority
shouldDecode = NO;
SDImageForceDecodePolicy policy = SDImageForceDecodePolicyAutomatic;
NSNumber *polivyValue = context[SDWebImageContextImageForceDecodePolicy];
if (polivyValue != nil) {
policy = polivyValue.unsignedIntegerValue;
}
if (shouldDecode) {
image = [SDImageCoderHelper decodedImageWithImage:image];
// TODO: Deprecated, remove in SD 6.0...
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
if (SD_OPTIONS_CONTAINS(options, SDWebImageAvoidDecodeImage)) {
policy = SDImageForceDecodePolicyNever;
}
#pragma clang diagnostic pop
image = [SDImageCoderHelper decodedImageWithImage:image policy:policy];
// assign the decode options, to let manager check whether to re-decode if needed
image.sd_decodeOptions = coderOptions;
}
Expand Down
70 changes: 68 additions & 2 deletions SDWebImage/Core/SDImageCoderHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,36 @@ typedef NS_ENUM(NSUInteger, SDImageCoderDecodeSolution) {
SDImageCoderDecodeSolutionUIKit
};

/// The policy to force-decode the origin CGImage (produced by Image Coder Plugin)
/// Some CGImage may be lazy, or not lazy, but need extra copy to render on screen
/// The force-decode step help to `pre-process` to get the best suitable CGImage to render, which can increase frame rate
/// The downside is that force-decode may consume RAM and CPU, and may loss the `lazy` support (lazy CGImage can be purged when memory warning, and re-created if need), see more: `SDImageCoderDecodeUseLazyDecoding`
typedef NS_ENUM(NSUInteger, SDImageForceDecodePolicy) {
/// Based on input CGImage's colorspace, alignment, bitmapinfo, if it may trigger `CA::copy_image` extra copy, we will force-decode, else don't
SDImageForceDecodePolicyAutomatic,
/// Never force decode input CGImage
SDImageForceDecodePolicyNever,
/// Always force decode input CGImage (only once)
SDImageForceDecodePolicyAlways
};

/// Byte alignment the bytes size with alignment
/// - Parameters:
/// - size: The bytes size
/// - alignment: The alignment, in bytes
static inline size_t SDByteAlign(size_t size, size_t alignment) {
return ((size + (alignment - 1)) / alignment) * alignment;
}

/// The pixel format about the information to call `CGImageCreate` suitable for current hardware rendering
///
typedef struct SDImagePixelFormat {
/// Typically is pre-multiplied RGBA8888 for alpha image, RGBX8888 for non-alpha image.
CGBitmapInfo bitmapInfo;
/// Typically is 32, the 8 pixels bytesPerRow.
size_t alignment;
} SDImagePixelFormat;

/**
Provide some common helper methods for building the image decoder/encoder.
*/
Expand All @@ -45,16 +75,31 @@ typedef NS_ENUM(NSUInteger, SDImageCoderDecodeSolution) {
*/
+ (NSArray<SDImageFrame *> * _Nullable)framesFromAnimatedImage:(UIImage * _Nullable)animatedImage NS_SWIFT_NAME(frames(from:));

#pragma mark - Preferred Rendering Format
/// For coders who use `CGImageCreate`, use the information below to create an effient CGImage which can be render on GPU without Core Animation's extra copy (`CA::Render::copy_image`), which can be debugged using `Color Copied Image` in Xcode Instruments
/// `CGImageCreate`'s `bytesPerRow`, `space`, `bitmapInfo` params should use the information below.
/**
Return the shared device-dependent RGB color space. This follows The Get Rule.
On iOS, it's created with deviceRGB (if available, use sRGB).
On macOS, it's from the screen colorspace (if failed, use deviceRGB)
Because it's shared, you should not retain or release this object.
Typically is sRGB for iOS, screen color space (like Color LCD) for macOS.
@return The device-dependent RGB color space
*/
+ (CGColorSpaceRef _Nonnull)colorSpaceGetDeviceRGB CF_RETURNS_NOT_RETAINED;

/**
Tthis returns the pixel format **Preferred from current hardward && OS using runtime detection**
@param containsAlpha Whether the image to render contains alpha channel
*/
+ (SDImagePixelFormat)preferredPixelFormat:(BOOL)containsAlpha;

/**
Check whether CGImage is hardware supported to rendering on screen, without the trigger of `CA::Render::copy_image`
You can debug the copied image by using Xcode's `Color Copied Image`, the copied image will turn Cyan and occupy double RAM for bitmap buffer.
Typically, when the CGImage's using the method above (`colorspace` / `alignment` / `bitmapInfo`) can render withtout the copy.
*/
+ (BOOL)CGImageIsHardwareSupported:(_Nonnull CGImageRef)cgImage;

/**
Check whether CGImage contains alpha channel.
Expand Down Expand Up @@ -113,20 +158,41 @@ typedef NS_ENUM(NSUInteger, SDImageCoderDecodeSolution) {
/**
Return the decoded image by the provided image. This one unlike `CGImageCreateDecoded:`, will not decode the image which contains alpha channel or animated image. On iOS 15+, this may use `UIImage.preparingForDisplay()` to use CMPhoto for better performance than the old solution.
@param image The image to be decoded
@note This translate to `decodedImageWithImage:policy:` with automatic policy
@return The decoded image
*/
+ (UIImage * _Nullable)decodedImageWithImage:(UIImage * _Nullable)image;

/**
Return the decoded image by the provided image. This one unlike `CGImageCreateDecoded:`, will not decode the image which contains alpha channel or animated image. On iOS 15+, this may use `UIImage.preparingForDisplay()` to use CMPhoto for better performance than the old solution.
@param image The image to be decoded
@param policy The force decode policy to decode image, will effect the check whether input image need decode
@return The decoded image
*/
+ (UIImage * _Nullable)decodedImageWithImage:(UIImage * _Nullable)image policy:(SDImageForceDecodePolicy)policy;

/**
Return the decoded and probably scaled down image by the provided image. If the image pixels bytes size large than the limit bytes, will try to scale down. Or just works as `decodedImageWithImage:`, never scale up.
@warning You should not pass too small bytes, the suggestion value should be larger than 1MB. Even we use Tile Decoding to avoid OOM, however, small bytes will consume much more CPU time because we need to iterate more times to draw each tile.
@param image The image to be decoded and scaled down
@param bytes The limit bytes size. Provide 0 to use the build-in limit.
@note This translate to `decodedAndScaledDownImageWithImage:limitBytes:policy:` with automatic policy
@return The decoded and probably scaled down image
*/
+ (UIImage * _Nullable)decodedAndScaledDownImageWithImage:(UIImage * _Nullable)image limitBytes:(NSUInteger)bytes;

/**
Return the decoded and probably scaled down image by the provided image. If the image pixels bytes size large than the limit bytes, will try to scale down. Or just works as `decodedImageWithImage:`, never scale up.
@warning You should not pass too small bytes, the suggestion value should be larger than 1MB. Even we use Tile Decoding to avoid OOM, however, small bytes will consume much more CPU time because we need to iterate more times to draw each tile.
@param image The image to be decoded and scaled down
@param bytes The limit bytes size. Provide 0 to use the build-in limit.
@param policy The force decode policy to decode image, will effect the check whether input image need decode
@return The decoded and probably scaled down image
*/
+ (UIImage * _Nullable)decodedAndScaledDownImageWithImage:(UIImage * _Nullable)image limitBytes:(NSUInteger)bytes policy:(SDImageForceDecodePolicy)policy;

/**
Control the default force decode solution. Available solutions in `SDImageCoderDecodeSolution`.
@note Defaults to `SDImageCoderDecodeSolutionAutomatic`, which prefers to use UIKit for JPEG/HEIF, and fallback on CoreGraphics. If you want control on your hand, set the other solution.
Expand Down

0 comments on commit 8f16a63

Please sign in to comment.