Skip to content
This repository was archived by the owner on Aug 3, 2022. It is now read-only.

Remove all previous hack code. Create a SDWebImageFLCoder to decode just FLAnimatedImage for GIF. #2

Merged
merged 4 commits into from
Aug 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Example/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ EXTERNAL SOURCES:

CHECKOUT OPTIONS:
SDWebImage:
:commit: fe2fede60f9c6efcadfac8b90e8cae579656fffb
:commit: aff324b0d09ab4a1736807ca1abc549032b9238a
:git: https://github.com/rs/SDWebImage.git

SPEC CHECKSUMS:
Expand Down
4 changes: 3 additions & 1 deletion Example/SDWebImageFLPlugin/SDViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

#import "SDViewController.h"
#import <SDWebImageFLPlugin/SDWebImageFLPlugin.h>
#import <FLAnimatedImage/FLAnimatedImageView.h>

@interface SDViewController ()

Expand All @@ -18,6 +17,9 @@ @implementation SDViewController

- (void)viewDidLoad {
[super viewDidLoad];
[[SDImageCodersManager sharedManager] addCoder:[SDWebImageFLCoder sharedCoder]];


// Do any additional setup after loading the view, typically from a nib.
FLAnimatedImageView *animatedImageView = [[FLAnimatedImageView alloc] initWithFrame:self.view.frame];
animatedImageView.contentMode = UIViewContentModeScaleAspectFit;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,25 @@
#import "FLAnimatedImage.h"
#endif


/**
* A category for the FLAnimatedImage imageView class that hooks it to the SDWebImage system.
* Very similar to the base class category (UIImageView (WebCache))
*/
@interface FLAnimatedImageView (WebCache)

/**
* Optimal frame cache size of FLAnimatedImage during initializer. (1.0.11 version later)
* This value will help you set `optimalFrameCacheSize` arg of FLAnimatedImage initializer after image load.
* Defaults to 0.
*/
@property (nonatomic, assign) NSUInteger sd_optimalFrameCacheSize;

FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextOptimalFrameCacheSize;
/**
* Predrawing control of FLAnimatedImage during initializer. (1.0.11 version later)
* This value will help you set `predrawingEnabled` arg of FLAnimatedImage initializer after image load.
* Defaults to YES.
*/
@property (nonatomic, assign) BOOL sd_predrawingEnabled;
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextPredrawingEnabled;


/**
* Cache control for associated FLAnimatedImage object for memory cache. When enabled, we will bind created FLAnimatedImage instance to UIImage, and store it into memory cache to avoid create this instance cause decoding performance. See `UIImage+FLAnimatedImage`.
* When enabled, this may consume more memory, if you are facing memory issue, disable it and let FLAnimatedImage been created just in time and dealloced as it not been used. However, when disabled, this may impact performance since we need query disk cache, create FLAnimatedImage and decoding even when the current GIF url is cached.
* Defatuls to YES;
* A category for the FLAnimatedImage imageView class that hooks it to the SDWebImage system.
* Very similar to the base class category (UIImageView (WebCache))
*/
@property (nonatomic, assign) BOOL sd_cacheFLAnimatedImage;
@interface FLAnimatedImageView (WebCache)

/**
* Load the image at the given url (either from cache or download) and load it in this imageView. It works with both static and dynamic images
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,49 +7,11 @@
*/

#import "FLAnimatedImageView+WebCache.h"
#import "objc/runtime.h"

@implementation FLAnimatedImageView (WebCache)

// These property based options will moved to `SDWebImageContext` in 5.x, to allow per-image-request level options instead of per-imageView-level options
- (NSUInteger)sd_optimalFrameCacheSize {
NSUInteger optimalFrameCacheSize = 0;
NSNumber *value = objc_getAssociatedObject(self, @selector(sd_optimalFrameCacheSize));
if ([value isKindOfClass:[NSNumber class]]) {
optimalFrameCacheSize = value.unsignedShortValue;
}
return optimalFrameCacheSize;
}

- (void)setSd_optimalFrameCacheSize:(NSUInteger)sd_optimalFrameCacheSize {
objc_setAssociatedObject(self, @selector(sd_optimalFrameCacheSize), @(sd_optimalFrameCacheSize), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (BOOL)sd_predrawingEnabled {
BOOL predrawingEnabled = YES;
NSNumber *value = objc_getAssociatedObject(self, @selector(sd_predrawingEnabled));
if ([value isKindOfClass:[NSNumber class]]) {
predrawingEnabled = value.boolValue;
}
return predrawingEnabled;
}

- (void)setSd_predrawingEnabled:(BOOL)sd_predrawingEnabled {
objc_setAssociatedObject(self, @selector(sd_predrawingEnabled), @(sd_predrawingEnabled), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (BOOL)sd_cacheFLAnimatedImage {
BOOL cacheFLAnimatedImage = YES;
NSNumber *value = objc_getAssociatedObject(self, @selector(sd_cacheFLAnimatedImage));
if ([value isKindOfClass:[NSNumber class]]) {
cacheFLAnimatedImage = value.boolValue;
}
return cacheFLAnimatedImage;
}
SDWebImageContextOption _Nonnull const SDWebImageContextOptimalFrameCacheSize = @"optimalFrameCacheSize";
SDWebImageContextOption _Nonnull const SDWebImageContextPredrawingEnabled = @"predrawingEnabled";

- (void)setSd_cacheFLAnimatedImage:(BOOL)sd_cacheFLAnimatedImage {
objc_setAssociatedObject(self, @selector(sd_cacheFLAnimatedImage), @(sd_cacheFLAnimatedImage), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@implementation FLAnimatedImageView (WebCache)

- (void)sd_setImageWithURL:(nullable NSURL *)url {
[self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil];
Expand Down Expand Up @@ -85,48 +47,31 @@ - (void)sd_setImageWithURL:(nullable NSURL *)url
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
SDWebImageMutableContext *mutableContext;
if (context) {
mutableContext = [context mutableCopy];
} else {
mutableContext = [NSMutableDictionary dictionary];
}
mutableContext[SDWebImageContextSetImageOperationKey] = NSStringFromClass(self.class);
__weak typeof(self)weakSelf = self;
[self sd_internalSetImageWithURL:url
placeholderImage:placeholder
options:options
context:context
context:mutableContext
setImageBlock:^(UIImage *image, NSData *imageData) {
__strong typeof(weakSelf)strongSelf = weakSelf;
if (!strongSelf) {
return;
}
// Step 1. Check memory cache (associate object)
FLAnimatedImage *associatedAnimatedImage = image.sd_FLAnimatedImage;
if (associatedAnimatedImage) {
// Asscociated animated image exist
strongSelf.animatedImage = associatedAnimatedImage;
strongSelf.image = nil;
return;
}
// Step 2. Check if original compressed image data is "GIF"
BOOL isGIF = (image.sd_imageFormat == SDImageFormatGIF || [NSData sd_imageFormatForImageData:imageData] == SDImageFormatGIF);
if (!isGIF) {
strongSelf.image = image;
strongSelf.animatedImage = nil;
return;
}
// Step 3. Check if data exist or query disk cache
if (!imageData) {
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:url];
imageData = [[SDImageCache sharedImageCache] diskImageDataForKey:key];
}
// Step 4. Create FLAnimatedImage
FLAnimatedImage *animatedImage = [[FLAnimatedImage alloc] initWithAnimatedGIFData:imageData optimalFrameCacheSize:strongSelf.sd_optimalFrameCacheSize predrawingEnabled:strongSelf.sd_predrawingEnabled];
// Step 5. Set animatedImage or normal image
FLAnimatedImage *animatedImage = image.sd_FLAnimatedImage;
if (animatedImage) {
if (strongSelf.sd_cacheFLAnimatedImage) {
image.sd_FLAnimatedImage = animatedImage;
}
// FLAnimatedImage framework contains a bug that cause GIF been rotated if previous rendered image orientation is not Up. We have to call `setImage:` with non-nil image to reset the state. See `https://github.com/rs/SDWebImage/issues/2402`
strongSelf.image = animatedImage.posterImage;
strongSelf.animatedImage = animatedImage;
strongSelf.image = nil;
} else {
strongSelf.animatedImage = nil;
strongSelf.image = image;
strongSelf.animatedImage = nil;
}
}
progress:progressBlock
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

#import <SDWebImage/SDWebImage.h>

// A coder which decode the GIF image, into `FLAnimatedImage` representation and bind the associated object. See `UIImage+SDWebImageFLPlugin` for more detailed information.
// When you want to use `FLAnimatedImageView` to load image, be sure to add this coder before `SDImageGIFCoder`, to ensure this coder been processed before `SDImageGIFCoder`

@interface SDWebImageFLCoder : NSObject <SDImageCoder>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some thoughts after I looked coder mechanism.
The coder mechanism is sufficient currently? Maybe we can add the url parameter and a context option which can specify the coder class per image.

Copy link
Collaborator Author

@dreampiggy dreampiggy Jul 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zhongwuzw You can pass anything into context, and get the context back. This is a context pattern (The context arg pass from really top-level API to the bottom). For example, pass the url during sd_setImageWithURL:

[imageView sd_setImageWithURL:url placeholderImage:nil context:@{MyURLKey: url}];
// Then you can get the `MyURLKey` from custom coder
SDWebImageContext *context = options[SDImageCoderWebImageContext];
NSURL *url = context[MyURLKey];

Actually it's a bad design. A image coder, it's designed to solve the image decoding task, but not any business logic related to things other than decoding. However, we leave it as a hackable space, to enable some advanced user use this to do extra logic.

All built-in coders, should not touch SDWebImageContext during image decoding, if new option is requsted, update SDImageCoderOptions

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dreampiggy Emm, I mean bring the context ahead to - (BOOL)canDecodeFromData:(NSData *)data, which can determine wether or not can handle image in advance.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zhongwuzw That too much. Your image coder should only judge the image format and choose to response it or not. Like Image/IO using UTType to determine which underneath plugin (AppleJPEGPlugin ? PNGPlugin ?) should use. So it should be been passed with the SDImageCoderOptions arg.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dreampiggy fine, no big difference.


@property (nonatomic, class, readonly, nonnull) SDWebImageFLCoder *sharedCoder;

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

#import "SDWebImageFLCoder.h"
#import "FLAnimatedImageView+WebCache.h"

@implementation SDWebImageFLCoder

+ (SDWebImageFLCoder *)sharedCoder {
static SDWebImageFLCoder *coder;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
coder = [[SDWebImageFLCoder alloc] init];
});
return coder;
}

- (BOOL)canDecodeFromData:(NSData *)data {
return ([NSData sd_imageFormatForImageData:data] == SDImageFormatGIF);
}

- (UIImage *)decodedImageWithData:(NSData *)data options:(SDImageCoderOptions *)options {
SDWebImageContext *context = options[SDImageCoderWebImageContext];
NSString *operationKey = context[SDWebImageContextSetImageOperationKey];
Class imageViewClass = NSClassFromString(operationKey);

// Check if image request come from `FLAnimatedImageView`
if (imageViewClass && [imageViewClass isSubclassOfClass:FLAnimatedImageView.class]) {
// Parse args
BOOL predrawingEnabled = YES;
if (context[SDWebImageContextPredrawingEnabled]) {
predrawingEnabled = [context[SDWebImageContextPredrawingEnabled] boolValue];
}
NSUInteger optimalFrameCacheSize = 0;
if (context[SDWebImageContextOptimalFrameCacheSize]) {
optimalFrameCacheSize = [context[SDWebImageContextOptimalFrameCacheSize] unsignedIntegerValue];
}
// Create FLAnimatedImage
FLAnimatedImage *animatedImage = [[FLAnimatedImage alloc] initWithAnimatedGIFData:data optimalFrameCacheSize:optimalFrameCacheSize predrawingEnabled:predrawingEnabled];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the meaning ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't support subclass.

@interface SubclassOfFLAnimatedImageView : FLAnimatedImageView
@end

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, this line don't needs to consider subclass. Just this line.

Copy link
Collaborator Author

@dreampiggy dreampiggy Jul 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zhongwuzw Added one commit to support subclass.

Anyway, most of users does not use subclass of FLAnimatedImageView. However, this is possible and support on it is easy

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's true, normally, maybe only big companies would do some encapsulation, they maybe would use AFNetWorkingSDWebImage...., but would not use it in code directly, the mainly reason is keep the third parties in control, e.x., if third framework changes API or definitely incompatible, they only need change the part of encapsulation, no hurt to the entire code base.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I'm sensitive to these things. The class would always better than direct String compare. 😂

if (!animatedImage) {
return nil;
}

return [UIImage sd_imageWithFLAnimatedImage:animatedImage];
} else {
UIImage *image;
NSArray<id<SDImageCoder>> *coders = [SDImageCodersManager sharedManager].coders;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dreampiggy TBO, I don't much like handle like this, it's a little bit non-decouple very well, my opinion is if SDWebImageFLCoder or any other coder can't create the image, just return nil, SDImageCodersManager can check if it is nil, it would enumerate next coder to check wether can decode. I can make a PR if you think it deserve. 😂
What's your opinion? 🤔

Copy link
Collaborator Author

@dreampiggy dreampiggy Jul 31, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zhongwuzw The image coder protocol, it's not designed for SDImageCodersManager...SDImageCodersManager is just a implmentation based on that protocol to provide multiple coder system. And, these two methods we talk about, does not represent the same step during image decoding. I can describe why we need both of two.

The decodedImageWithData:options: return nil, does not means : This coder can not support this compressed image foramt. You should realize this meaning.

For example, during image decoding, you are trying to parse the information from binary data for target image format (EXIF, etc), but it failed.
For example, during allocation of large memory for bitmap buffer, the allocation failed.
For example, during animation frame looping, there are one frame can not been decoded and it failed.

These cases, should not call another coder to process the samething. Because it's a internal error happend for that coder, but not image foramt.

However, the canDecodeFromData: it's about whether this coder can have ability to decode target image format. Actually it should be named with canDecodedFromFormat:, but using a NSData * make it more extensible because we can't not provide all SDImageForamt or list all the File Signature for image format in code.

So, I still propose to keep these two protocol seperate. The decodedImageWithData can have a options arg which about the options for image decoding. However, the canDecodeFromData: should not contains the options arg. It's only things about whether this coder can support this image format, but not whether current system status and data can allow coder to successfully produce a UIImage.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dreampiggy 👍 Yeah, I know your opinion. Maybe most of the user would not use multiple coder for each image format. e.x. we have SDImageGIFCoder and SDWebImageFLCoder for the same GIF format.
When I see these code, I just feel that SDWebImageFLCoder shouldn't know what SDImageCodersManager is, it just needs to decode data IMO.

My opinion for SDImageCodersManager is it would manage coder group by image format. Other words, SDWebImageFLCoder and SDImageGIFCoder is a tuple, maybe SDImageAPNGCoder and Custom APNGCoder is an another tuple. SDImageCodersManager would try image in specific image coder group, like GIF, SDImageCodersManager would try decode in SDWebImageFLCoder firstly, because it's added by user, if it return nil, would try SDImageGIFCoder next. And also we can provide options to let user re-disable this behavior.

At last. It's also good to keep the current implementation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dreampiggy I suddenly realized that maybe Apple do the same as my thought. Apple has a manager to figure out what the image format the data is, if it is JPEG, it would call JPEGPlugin to decode, if it is PNG, it would call PNGPlugin. Other than enumerate all plugins to ask them wether they can decode the data. Just like broadcast vs peer to peer.

Copy link
Collaborator Author

@dreampiggy dreampiggy Jul 31, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zhongwuzw No. ImageIO is just a Core Foundation wrapper for the C++ framework IIOReadPlugin && IIOWritePlugin. It actually use the same design from SDWebImage's coder. They still need to enumerate each plugins.

IIOReadPlugin use a array (std::vector) to store each of plugins (Which subclass of IIOGeneric_Reader abstract class). And for each plugin, call their handle method instead. When you call CGImageSourceCreateWithXXX, it will try to bind the Core Foundation CGImageSourceRef instance to a C++ IIOReadPlugin plugin internally (Just like a proxy).

The good point it's that Apple can hard-coded all the plugins into single files because they are an entire framework, but we can't. We open the posibility to let other third-party developers to provide a coder plugin. So this is why we can't do the same thing to hold a central check of UTType and choose which coder should been used.

If you're interested in the detailed implementation, You can use Hopper to deassemble ImageIO.framework. Here is just some snippet from my previous experience.

Copy link
Collaborator Author

@dreampiggy dreampiggy Jul 31, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example, they register all plugins during IIO_ReaderHandler::buildPluginList() lazy init method, like we recommend user to register coders to SDImageCodersManager at lauch time.

int __ZN17IIO_ReaderHandler15buildPluginListEv() {
    r13 = rdi;
    if (*_gIIODebugFlagsInitializer != 0xffffffffffffffff) {
            dispatch_once(_gIIODebugFlagsInitializer, ^ {/* block implemented at ____ZN17IIO_ReaderHandler15buildPluginListEv_block_invoke */ } });
    }
    r14 = 0x0;
    var_38 = r14;
    var_34 = r14;
    if (dyld_process_is_restricted() == 0x0) {
            r14 = getenv("RAWCAMERA_BUNDLE_PATH");
    }
    if ((*(int8_t *)0x539f42 & 0x4) == 0x0) {
            if (r14 != 0x0) {
                    r15 = dlopen(r14, 0x1);
                    if (r15 == 0x0) {
                            r15 = dlopen("/System/Library/CoreServices/RawCamera.bundle/RawCamera", 0x1);
                            if (r15 != 0x0) {
                                    rcx = dlsym(r15, "GetRawPluginsInfo");
                                    if (rcx != 0x0) {
                                            rax = (rcx)(&var_38, &var_34);
                                    }
                                    else {
                                            rax = 0x0;
                                    }
                                    var_40 = rax;
                                    *_gReadMakerNoteProps = dlsym(r15, "ReadMakerNoteProps");
                            }
                            else {
                                    var_40 = 0x0;
                            }
                    }
                    else {
                            rcx = dlsym(r15, "GetRawPluginsInfo");
                            if (rcx != 0x0) {
                                    rax = (rcx)(&var_38, &var_34);
                            }
                            else {
                                    rax = 0x0;
                            }
                            var_40 = rax;
                            *_gReadMakerNoteProps = dlsym(r15, "ReadMakerNoteProps");
                    }
            }
            else {
                    r15 = dlopen("/System/Library/CoreServices/RawCamera.bundle/RawCamera", 0x1);
                    if (r15 != 0x0) {
                            rcx = dlsym(r15, "GetRawPluginsInfo");
                            if (rcx != 0x0) {
                                    rax = (rcx)(&var_38, &var_34);
                            }
                            else {
                                    rax = 0x0;
                            }
                            var_40 = rax;
                            *_gReadMakerNoteProps = dlsym(r15, "ReadMakerNoteProps");
                    }
                    else {
                            var_40 = 0x0;
                    }
            }
    }
    else {
            var_40 = 0x0;
    }
    if (*IIO_ReaderHandler::UseAppleJPEG()::appleJPEGCheck != 0xffffffffffffffff) {
            dispatch_once(IIO_ReaderHandler::UseAppleJPEG()::appleJPEGCheck, ^ {/* block implemented at ____ZN17IIO_ReaderHandler12UseAppleJPEGEv_block_invoke */ } });
    }
    if (*(int8_t *)IIO_ReaderHandler::UseAppleJPEG()::gUseAppleJPEGPlugin != 0x0) {
            rax = CreateReader_AppleJPEG();
    }
    else {
            rax = CreateReader_JPEG();
    }
    var_30 = rax;
    if (rax != 0x0) {
            rcx = *(r13 + 0x18);
            if (rcx != *(r13 + 0x20)) {
                    *rcx = rax;
                    *(r13 + 0x18) = *(r13 + 0x18) + 0x8;
            }
            else {
                    void std::__1::vector<IIO_Reader*, std::__1::allocator<IIO_Reader*> >::__push_back_slow_path<IIO_Reader* const>(r13 + 0x10);
            }
    }
    rax = CreateReader_PNG();
    var_30 = rax;
    if (rax != 0x0) {
            rcx = *(r13 + 0x18);
            if (rcx != *(r13 + 0x20)) {
                    *rcx = rax;
                    *(r13 + 0x18) = *(r13 + 0x18) + 0x8;
            }
            else {
                    void std::__1::vector<IIO_Reader*, std::__1::allocator<IIO_Reader*> >::__push_back_slow_path<IIO_Reader* const>(r13 + 0x10);
            }
    }
    rax = CreateReader_GIF();
    var_30 = rax;
    if (rax != 0x0) {
            rcx = *(r13 + 0x18);
            if (rcx != *(r13 + 0x20)) {
                    *rcx = rax;
                    *(r13 + 0x18) = *(r13 + 0x18) + 0x8;
            }
            else {
                    void std::__1::vector<IIO_Reader*, std::__1::allocator<IIO_Reader*> >::__push_back_slow_path<IIO_Reader* const>(r13 + 0x10);
            }
    }
    r12 = var_34;
    if (r12 > 0x0) {
            r15 = r13 + 0x10;
            rbx = 0x0;
            do {
                    rax = operator new(0x60);
                    rcx = *(var_40 + rbx * 0x8);
                    xmm0 = intrinsic_movups(xmm0, *(int128_t *)rcx);
                    rdx = *(rcx + 0x10);
                    rsi = *(rcx + 0x60);
                    *(int128_t *)(rax + 0x8) = intrinsic_movups(*(int128_t *)(rax + 0x8), xmm0);
                    *(rax + 0x18) = rdx;
                    *(rax + 0x20) = rsi;
                    *(int8_t *)(rax + 0x28) = 0x1;
                    *rax = 0x527c18;
                    xmm0 = intrinsic_movups(xmm0, *(int128_t *)(rcx + 0x18));
                    *(int128_t *)(rax + 0x30) = intrinsic_movups(*(int128_t *)(rax + 0x30), xmm0);
                    xmm0 = intrinsic_movups(xmm0, *(int128_t *)(rcx + 0x28));
                    *(int128_t *)(rax + 0x40) = intrinsic_movups(*(int128_t *)(rax + 0x40), xmm0);
                    xmm0 = intrinsic_movups(xmm0, *(int128_t *)(rcx + 0x38));
                    *(int128_t *)(rax + 0x50) = intrinsic_movups(*(int128_t *)(rax + 0x50), xmm0);
                    var_30 = rax;
                    rcx = *(r13 + 0x18);
                    if (rcx != *(r13 + 0x20)) {
                            *rcx = rax;
                            *(r13 + 0x18) = *(r13 + 0x18) + 0x8;
                    }
                    else {
                            void std::__1::vector<IIO_Reader*, std::__1::allocator<IIO_Reader*> >::__push_back_slow_path<IIO_Reader* const>(r15);
                            r12 = var_34;
                    }
                    rbx = rbx + 0x1;
            } while (rbx < sign_extend_64(r12));
    }
    rax = CreateReader_TIFF();
    var_30 = rax;
    if (rax != 0x0) {
            rcx = *(r13 + 0x18);
            if (rcx != *(r13 + 0x20)) {
                    *rcx = rax;
                    *(r13 + 0x18) = *(r13 + 0x18) + 0x8;
            }
            else {
                    void std::__1::vector<IIO_Reader*, std::__1::allocator<IIO_Reader*> >::__push_back_slow_path<IIO_Reader* const>(r13 + 0x10);
            }
    }
    rax = CreateReader_JP2();
    var_30 = rax;
    if (rax != 0x0) {
            rcx = *(r13 + 0x18);
            if (rcx != *(r13 + 0x20)) {
                    *rcx = rax;
                    *(r13 + 0x18) = *(r13 + 0x18) + 0x8;
            }
            else {
                    void std::__1::vector<IIO_Reader*, std::__1::allocator<IIO_Reader*> >::__push_back_slow_path<IIO_Reader* const>(r13 + 0x10);
            }
    }
    // many lines of add plugin
    // many lines of add plugin
    // many lines of add plugin
    rax = *(r13 + 0x10);
    rcx = *(r13 + 0x18);
    if (rax != rcx) {
            rdx = *(r13 + 0x8);
            do {
                    rsi = rdx;
                    rdx = *rax;
                    rdx = *(rdx + 0x18);
                    CMP(rsi, rdx);
                    asm { cmova      rdx, rsi };
                    rax = rax + 0x8;
            } while (rcx != rax);
            *(r13 + 0x8) = rdx;
    }
    return rax;
}

Copy link
Collaborator Author

@dreampiggy dreampiggy Jul 31, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example, they do for-loop to check first 16-bytes of File Signature (See above), to determine which plugin should be binded for current CGImageSource during IIO_ReaderHandler::readerForBytes(). Each plugins contains their UTType, so they just need to parse the bytes to UTType, and then enumerate the array to check which plugin should be binded.

There are also some other check based on the UTType string in IIO_ReaderHandler::readerForUTType(), or even based on the filename extension like IIO_ReaderHandler::readerForPathExtension()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dreampiggy Maybe you are more familiar than me at ImageIO, I just see it start last hours before 😂 . Am I missed something? I profile using Instruments first, the result is below, IIOImageSource::createImageAtIndex would use CGImageSource to create CGImageRef, then I trace the steps, I didn't find any for loop to find the plugin. I can step into it more detail carefully, may I miss anything?

I use CGImageSourceCreateWithData and CGImageSourceCreateImageAtIndex to create image.

303.00 ms   22.2%	303.00 ms	 	 aj_icol_mcurow_cmyk
303.00 ms   22.2%	0 s	 	  aj_bufferproc_crop
303.00 ms   22.2%	0 s	 	   aj_decode_all
303.00 ms   22.2%	0 s	 	    aj_decode_all_mt
303.00 ms   22.2%	0 s	 	     applejpeg_decode_image_all
303.00 ms   22.2%	0 s	 	      AppleJPEGReadPlugin::copyImageBlockSet(InfoRec*, CGImageProvider*, CGRect, CGSize, __CFDictionary const*)
303.00 ms   22.2%	0 s	 	       AppleJPEGReadPlugin::CopyImageBlockSetProc(void*, CGImageProvider*, CGRect, CGSize, __CFDictionary const*)
303.00 ms   22.2%	0 s	 	        IIOImageProviderInfo::copyImageBlockSetWithOptions(CGImageProvider*, CGRect, CGSize, __CFDictionary const*)
303.00 ms   22.2%	0 s	 	         IIOImageProviderInfo::CopyImageBlockSetWithOptions(void*, CGImageProvider*, CGRect, CGSize, __CFDictionary const*)
303.00 ms   22.2%	0 s	 	          CGImageProviderCopyImageBlockSetWithOptions
159.00 ms   11.6%	0 s	 	           invocation function for block in IIOReadPlugin::cacheImmediately(__CFDictionary const*, CGImage*)
159.00 ms   11.6%	0 s	 	            _dispatch_client_callout
159.00 ms   11.6%	0 s	 	             _dispatch_queue_barrier_sync_invoke_and_complete
159.00 ms   11.6%	0 s	 	              IIOReadPlugin::cacheImmediately(__CFDictionary const*, CGImage*)
159.00 ms   11.6%	0 s	 	               IIOImageSource::createImageAtIndex(unsigned long, IIODictionary*)
159.00 ms   11.6%	0 s	 	                CGImageSourceCreateImageAtIndex

Copy link
Collaborator Author

@dreampiggy dreampiggy Aug 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zhongwuzw ...Paste many deassemble code here seems no use, and here on GitHub, maybe it's not a good place to talk about it. You can just use Hopper, search ImageIO.framework with that symbols.

Each plugins, in IIO_ReaderHandler::buildPluginList() (See above), call their create method, like CreateReader_HEIC(). In the CreateReader_HEIC(), it allocate a new plugin which is subclass of IIOGeneric_Reader. Then register the plugin with their desired UTType (For HEIC, it's the _kCGImageTypeIdentifierHEIC), and finally push back into the global std::vector.

During CGImageSourceGetCount / CGImageSourceCreateImageAtIndex and all the process method, it will lazy check whether a plugin has been binded to CGImageSource using IIOImageSource::bindToReader(). If not, it will call IIO_ReaderHandler::readerForBytes() once (For non-progressive decoding) to check File Signature, convert to UTType, and finally bind the plugin that match the UTType.

I don't know why you care about the loop. It just use a CFStringCompare and 16 Bytes hex check. But use a switch case will cause the code harder to maintain. (Consider a switch case contains 100 cases ?). Don't assume anything on performance until you do actual profile.

And anyway, it's just a personal experience, and deassemble code may not been considered a legal way of understanding the internal behavior for their framework. If you're interested in, just do yourself. Don't need to append more un-related things under this issue.

int __ZN17IIO_ReaderHandler15readerForUTTypeEPK10__CFString(void * arg0) {
    r15 = rsi;
    r12 = arg0;
    rbx = *(r12 + 0x10); 
    if (rbx == *(r12 + 0x18)) goto loc_10cc0e;

loc_10cbe5:
    r14 = 0x0;
    goto loc_10cbe8;

loc_10cbe8:
    if (CFStringCompare(**(*rbx + 0x8), r15, 0x0) == 0x0) goto loc_10cc13;

loc_10cc01:
    rbx = rbx + 0x8;
    if (rbx != *(r12 + 0x18)) goto loc_10cbe8;

loc_10cc16:
    rax = r14;
    return rax;

loc_10cc13:
    r14 = *rbx;
    goto loc_10cc16;

loc_10cc0e:
    r14 = 0x0;
    goto loc_10cc16;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dreampiggy Yeah, final words 😂 ,thanks for your comments, I looked the disassembling code already, you said system would enumerate all plugins to try to decode each image data. I think it's not , maybe from your recent comment, it's exactly what I want, it has the bind mechanism, just like a Dictionary, one-to-one relationship, TBO, I don't much care the performance of for loop in our coder manager, because it's trivial, what I cared is the pattern, that is manager needs to know to call which coder, other than in some coder, it asks manager to try to decode again.

Fine, I already learn too much. Thanks again ! ❤️

for (id<SDImageCoder> coder in coders.reverseObjectEnumerator) {
if (coder == self) {
continue;
}
if ([coder canDecodeFromData:data]) {
image = [coder decodedImageWithData:data options:options];
break;
}
}

return image;
}
}

- (BOOL)canEncodeToFormat:(SDImageFormat)format {
return NO;
}

- (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format options:(SDImageCoderOptions *)options {
return nil;
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,13 @@
*/
@property (nonatomic, strong, nullable) FLAnimatedImage *sd_FLAnimatedImage;

/**
Create a UIImage instance, which bind FLAnimatedImage using the `sd_FLAnimatedImage` on it.
This will use `posterImage` on FLAnimatedImage to create a new UIImage and specify the associate object. To avoid cycle retain.

@param animatedImage FLAnimatedImage instance
@return The UIImage which bind FLAnimatedImage on it
*/
+ (nullable instancetype)sd_imageWithFLAnimatedImage:(nullable FLAnimatedImage *)animatedImage;

@end
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

#import "UIImage+SDWebImageFLPlugin.h"
#import <SDWebImage/SDWebImage.h>
#import "objc/runtime.h"

@implementation UIImage (SDWebImageFLPlugin)
Expand All @@ -19,4 +20,18 @@ - (void)setSd_FLAnimatedImage:(FLAnimatedImage *)sd_FLAnimatedImage {
objc_setAssociatedObject(self, @selector(sd_FLAnimatedImage), sd_FLAnimatedImage, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

+ (instancetype)sd_imageWithFLAnimatedImage:(FLAnimatedImage *)animatedImage {
UIImage *posterImage = animatedImage.posterImage;
CGImageRef imageRef = posterImage.CGImage;
if (!imageRef) {
return nil;
}
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:posterImage.scale orientation:posterImage.imageOrientation];

image.sd_FLAnimatedImage = animatedImage;
image.sd_isDecoded = YES; // Avoid force decode and loss the associate object

return image;
}

@end
1 change: 1 addition & 0 deletions SDWebImageFLPlugin/Module/SDWebImageFLPlugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#import <SDWebImageFLPlugin/FLAnimatedImageView+WebCache.h>
#import <SDWebImageFLPlugin/UIImage+SDWebImageFLPlugin.h>
#import <SDWebImageFLPlugin/SDWebImageFLCoder.h>


FOUNDATION_EXPORT double SDWebImageFLPluginVersionNumber;
Expand Down