preface

Pictures predate words and are the most primitive means of information transmission. The idea of pictographic construction in the six Books is to draw out the physical features of the object to be expressed concretely with the lines or strokes of the text.

In His book Shuo Wen Jie Zi, Xu Shen said, “The hieroglyphs are drawn as their objects, followed by their artistic paintings, and the sun and moon are also.”

In the information transmission of modern society, pictures are still an indispensable part. Whether it is newspapers, magazines, comics and other physical publications or supermarket subway advertising activities in daily life, there will be special pictures to catch people’s attention.

In mobile apps, pictures usually occupy an important visual space. In terms of iOS development, all apps have elaborately designed AppIcon displayed in SpringBoard. When opening any mainstream App, there is always a dazzling array of pictures.

YYImage is a powerful iOS image framework (this project is one of THE COMPONENTS of YYKit), which supports the display and encoding/decoding of all the mainstream image formats in the market at present, and provides efficient dynamic memory cache management to ensure high performance and low memory animation playback.

YYKit author @ibireme has written two very good articles on iOS image processing that I recommend you check out before reading this article.

  • Mobile image format research
  • IOS handles small tips for images

The code cited in this paper is YYImage V1.0.4 version of the source code, the article aims to analyze the architecture and design ideas of YYImage and the author found in the process of reading the source code to explore and share the interesting implementation details, will not be translated line by line source. It is recommended that students who are interested in source code implementation should combine YYImage V1.0.4 version of the source code to eat this article ~

The index

  • YYImage profile
  • YYImage, YYFrameImage, YYSpriteSheetImage
  • YYAnimatedImageView
  • YYImageCoder
  • conclusion
  • Further reading

YYImage profile

YYImage is a powerful iOS image framework, supporting the current mainstream static/dynamic image encoding/decoding and dynamic image animation display, which has the following features:

  • Supports playback/encoding/decoding of the following types of animated images: WebP, APNG, GIF.
  • Support the following types of static image display/encoding/decoding: WebP, PNG, GIF, JPEG, JP2, TIFF, BMP, ICO, ICNS.
  • Supports progressive/progressive scan/interlaced scan decoding of the following types of images: PNG, GIF, JPEG, BMP.
  • Support multiple picture frame animation playback, support single picture Sprite sheet animation.
  • Efficient dynamic memory cache management to ensure high performance and low memory animation playback.
  • Fully compatible with UIImage and UIImageView, easy to use.
  • Retain an extensible interface to support custom animations.
  • Each class and method is well documented.

YYImage architecture analysis

YYImage source code can be divided into three levels according to its corresponding relationship with UIKit:

The hierarchy UIKit YYImage
The image layer UIImage YYImage, YYFrameImage, YYSpriteSheetImage
The view layer UIImageView YYAnimatedImageView
Encoding/decoding layer ImageIO.framework YYImageCoder
  • The image layer encapsulates different types of image information into classes and provides initialization and other convenient interfaces.
  • The view layer is responsible for the display of the content of the image layer (including the animation of the dynamic image).
  • Encoding/decoding layer, providing image bottom support, so that the entire framework can support the mainstream image formats in the market.

Note: Imageio. framework is an image codec library implemented by iOS, responsible for managing colors and accessing image metadata. Its internal implementation uses third-party codec libraries (such as libpng, etc.) and tweaks and optimizations to third-party libraries. In addition, iOS has developed appleJpeg. framework specifically for JPEG encoding/decoding to achieve higher performance hard encoding and hard decoding.

YYImage, YYFrameImage, YYSpriteSheetImage

First, three classes of image layer in YYImage library are introduced, which are:

  • YYImage
  • YYFrameImage
  • YYSpriteSheetImage

YYImage

YYImage is an advanced class for displaying dynamic image data. It inherits from UIImage and extends UIImage to support image decoding in WebP, APNG and GIF formats. It also supports NSCoding protocol to archive and unarchive multi-frame image data.

@interface YYImage : UIImage <YYAnimatedImage>

+ (nullable YYImage *)imageNamed:(NSString *)name; Unlike UIImage, this method has no cache
+ (nullable YYImage *)imageWithContentsOfFile:(NSString *)path;
+ (nullable YYImage *)imageWithData:(NSData *)data;
+ (nullable YYImage *)imageWithData:(NSData *)data scale:(CGFloat)scale;

@property (nonatomic.readonly) YYImageType animatedImageType; // Image data type
@property (nullable.nonatomic.readonly) NSData *animatedImageData; // Dynamic image metadata
@property (nonatomic.readonly) NSUInteger animatedImageMemorySize; // Multi-frame image memory usage
@property (nonatomic) BOOL preloadAllAnimatedImageFrames; // Preload all frames (to memory)

@end
Copy the code

YYImage provides uiImage-like initialization methods and exposes some properties for us to detect and control its memory usage.

It is worth mentioning that YYImage’s imageNamed: the initialization method does not support caching. Because its imageNamed: internal implementation is different from UIImage’s imageNamed: method, the implementation process in YYImage is as follows:

  • Deduce the given image resource path
  • Get NSData in the path
  • Calling YYImageinitWithData:scale:Method initialization

YYImage’s private variable part is also relatively simple, I believe you can guess according to the above exposed attributes and interfaces.

@implementation YYImage {
    YYImageDecoder *_decoder; / / decoder
    NSArray *_preloadedFrames; // Preloaded image frames
    dispatch_semaphore_t _preloadedLock; // Preload the lock
    NSUInteger _bytesPerFrame; // Memory usage
}
Copy the code

We know that dispatch_semaphoRE_T can be used as a lock when the semaphore is 1, and it is very efficient as a lock when it is not blocked. The main purpose of using _preloadedLock here is to ensure that _preloadedFrames can be read and written. Since the reading and writing process of _preloadedFrames is completed in memory, the operation will not take too long, so it will not be blocked for a long time. Dispatch_semaphore_t is appropriate for this situation.

~ _preloadedFrames corresponding preloadAllAnimatedImageFrames attribute, the preload all the frames to memory, _preloadedFrames as an array will save all the images. _bytesPerFrame corresponds to the animatedImageMemorySize property. When initializing YYImage, if the total number of frames exceeds 1, the size of _bytesPerFrame will be calculated.

if (decoder.frameCount > 1) {
    _decoder = decoder;
    _bytesPerFrame = CGImageGetBytesPerRow(image.CGImage) * CGImageGetHeight(image.CGImage);
    _animatedImageMemorySize = _bytesPerFrame * decoder.frameCount;
}
Copy the code

Actually YYImage and some implementation is more interesting, such as animatedImageDurationAtIndex: if you take in the implementation of to < = 10 ms will be replaced by 100 ms, and explains why in the comments (certain key points in look, smile ~).

YYFrameImage

YYFrameImage is a class specifically designed to display frame-based animated images, which is also a subclass of UIImage. YYFrameImage supports only system image formats such as PNG and JPEG.

Note: Animated images displayed with YYFrameImage should also be played based on YYAnimatedImageView.

@interface YYFrameImage : UIImage <YYAnimatedImage>

- (nullable instancetype)initWithImagePaths:(NSArray<NSString *> *)paths
                           oneFrameDuration:(NSTimeInterval)oneFrameDuration
                                  loopCount:(NSUInteger)loopCount;
- (nullable instancetype)initWithImagePaths:(NSArray<NSString *> *)paths
                             frameDurations:(NSArray<NSNumber *> *)frameDurations
                                  loopCount:(NSUInteger)loopCount;
- (nullable instancetype)initWithImageDataArray:(NSArray<NSData *> *)dataArray
                               oneFrameDuration:(NSTimeInterval)oneFrameDuration
                                      loopCount:(NSUInteger)loopCount;
- (nullable instancetype)initWithImageDataArray:(NSArray<NSData *> *)dataArray
                                 frameDurations:(NSArray *)frameDurations
                                      loopCount:(NSUInteger)loopCount;

@end
Copy the code

YYFrameImage allows static images such as PNG and JPEG to be displayed as dynamic images by switching frames, and provides four commonly used initialization methods.

There are some basic variables inside YYFrameImage that correspond to the four commonly used initialization interfaces it exposes:

@implementation YYFrameImage {
    NSUInteger _loopCount;
    NSUInteger _oneFrameBytes;
    NSArray *_imagePaths;
    NSArray *_imageDatas;
    NSArray *_frameDurations;
}
Copy the code

The implementation code of YYFrameImage is very simple, and the initialization method can be roughly divided into the following steps:

  • Into the reference check
  • The first image is fetched according to the input parameter
  • Class with the header diagram_oneFrameBytes, such as the input parameter initialization_imageDatas_frameDurations_loopCount
  • withUIImageinitWithCGImage:scale:orientation:Class and return the initialization result

YYSpriteSheetImage

YYSpriteSheetImage is an image class for Spritesheet animation display, which is also a subclass of UIImage.

Spritesheet will be familiar to those of you who have done game development or worked on simple web game demos in the past. Its animation principle is to decompose an animation process into multiple animation frames, and arrange them sequentially on a large canvas. When playing the animation, we only need to extract the corresponding frame from the canvas according to the size of each frame image and the corresponding index to replace the display so as to achieve the effect of human eyes judging the animation. Click on An Introduction to Spritesheet Animation or What is a Spritesheet? Learn more about Spritesheet animations.

Note: There is a tool SpriteSheetMaker recommends for making a SpriteSheet material.

@interface YYSpriteSheetImage : UIImage <YYAnimatedImage>

// The initialization method, which might be a bit tedious for those of you who are new to Spritesheet
- (nullable instancetype)initWithSpriteSheetImage:(UIImage *)image
                                     contentRects:(NSArray<NSValue *> *)contentRects
                                   frameDurations:(NSArray<NSNumber *> *)frameDurations
                                        loopCount:(NSUInteger)loopCount;

@property (nonatomic.readonly) NSArray<NSValue *> *contentRects; // Frame position information
@property (nonatomic.readonly) NSArray<NSValue *> *frameDurations; // Frame duration
@property (nonatomic.readonly) NSUInteger loopCount; / / cycle number

// Select frame CALayer from index
- (CGRect)contentsRectForCALayerAtIndex:(NSUInteger)index;

@end
Copy the code

The input parameters of the initialization method are SpriteSheet canvas (containing a large image of all animation frames) image, position contentRects of each frame, duration frameDurations of each frame, loopCount, The initialization example is written in the yyspritesheetimage.h comment of the YYImage source file.

Note: below to tell YYAnimatedImageView in defines YYAnimatedImage agreement, this agreement is an optional method animatedImageContentsRectAtIndex: It’s made for YYSpriteSheetImage.

Here the need to mention contentsRectForCALayerAtIndex: The interface will find the CALayer position of the corresponding frame according to the index. The interface returns a layer consisting of values between 0.0 and 1.0 to position LayerRect. If an exception is found during position search, CGRectMake(0, 0, 1, 1) is returned.

  • Check whether the input index exceeds the total number of SpriteSheet split frames, and return CGRectMake(0, 0, 1, 1)
  • No more than YYAnimatedImage protocol throughanimatedImageContentsRectAtIndex:Method to find the real location of the corresponding index RealRect
  • The logical positioning LogicRect of the specified index frame is obtained by miscalculating the value between 0.0 and 1.0 between the real location RealRect and the SpriteSheet canvas
  • throughCGRectIntersectionMethod computes the intersection of LogicRect and CGRectMake(0, 0, 1, 1) to ensure that the logical location does not extend beyond the canvas
  • Return the processed logical positioning LogicRect as layer positioning LayerRect

The LayerRect returned exists as the relative position within the canvas of the corresponding index frame, which can be combined with the canvas to locate the specific size and position of the corresponding frame image.

YYAnimatedImageView

The animation presented in people’s eyes is formed by sequential replacement of a series of coherent images in a short time. Therefore, to display animation, we only need to know the information of each frame of image in the animation sequence and the corresponding display time. The contents corresponding to UIImage hierarchy in YYImage (YYImage, YYFrameImage, and YYSpriteSheetImage) have been introduced above. Although there are differences between them in content and form, the principle of human eye animation rendering remains unchanged.

YYAnimatedImageView is an important component of YYImage. It is a subclass of UIImageView and is responsible for the view display of different image classes in the image layer of YYImage (including the animation playback of dynamic images). It contains YYAnimatedImage protocol and YYAnimatedImageView itself.

YYAnimatedImage agreement

As mentioned above, whether YYImage, YYFrameImage, YYSpriteSheetImage or other image classes that may be expanded in the future, although there are differences between them in content and form, the principle of human eye animation rendering remains unchanged.

YYAnimatedImage protocol is to find out the commonalities between different image classes without affecting the original image classes. (laughter), output the basic information required for the rendering of human eye animation to the protocol used by YYAnimatedImageView through a unified interface.

Note: As an image class you must follow the YYAnimatedImage protocol so that you can use YYAnimatedImageView to play animations.

@protocol YYAnimatedImage <NSObject>

@required
// Total number of animation frames
- (NSUInteger)animatedImageFrameCount;
// Animation loop number, 0 means infinite loop
- (NSUInteger)animatedImageLoopCount;
// Number of bytes per frame (in memory), which may be used to optimize memory buffer size
- (NSUInteger)animatedImageBytesPerFrame;
// Returns the frame image for the given special index. This method may be called in an asynchronous thread
- (nullable UIImage *)animatedImageFrameAtIndex:(NSUInteger)index;
// Returns the display duration of the frame image corresponding to the given special index
- (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index;

@optional
// A method for Spritesheet animation that displays the position of a frame image in the Spritesheet canvas
- (CGRect)animatedImageContentsRectAtIndex:(NSUInteger)index;

@end
Copy the code

Above mentioned optional animatedImageContentsRectAtIndex: implement interface is designed for Spritesheet animation design.

The design idea of specifying a protocol like this so that unrelated classes follow the protocol and have a unified functional interface to facilitate the call of another class can be used in many scenarios in the development process of our daily projects. For example, we can encapsulate a TableView and design a TableViewCell protocol. Let all tableViewCells implement this protocol to have a unified interface, and then our encapsulated TableView class can use those TableViewCells to display data uniformly. Save the labor of writing the same function UITableView over and over again.

YYAnimatedImageView

As mentioned above, YYAnimatedImageView is the image view layer in THE YYImage framework, which is connected by the image layer and the encoding/decoding layer. It is a hub. , we need to focus on its internal implementation:

@interface YYAnimatedImageView : UIImageView

// If the image consists of multiple frames, the value is automatically assigned to YES, and the animation can be automatically played and stopped during show and hide
@property (nonatomic) BOOL autoPlayAnimatedImage;
// The current frame (starting from 0) will be displayed immediately after setting the new value. If the new value is invalid, this method will not work
@property (nonatomic) NSUInteger currentAnimatedImageIndex;
// Whether the animation is currently playing
@property (nonatomic.readonly) BOOL currentIsPlayingAnimation;
// The animation timer's runloop mode, which defaults to NSRunLoopCommonModes, is related to the animation timer's firing
@property (nonatomic.copy) NSString *runloopMode;
The default value is 0 (dynamic). If there is a value, the cache will be limited to the value. When a memory warning is received or the App enters the background, the cache will be released immediately and returned to its original size in due course
@property (nonatomic) NSUInteger maxBufferSize;

@end
Copy the code

Er… Surprisingly simple ~ only a few properties are exposed so that we can see the animation’s playing status and memory usage in real time during use. A rule of thumb I have drawn from looking at the source code is that if a component is prominent in the library, the simpler the content exposed in its.h file, the more complex its.m internal implementation is.

YYAnimatedImageView can be animated by RunLoop, and most likely by NSTimer or CADisplayLink. Now let’s analyze the implementation of YYAnimatedImageView to verify our conjecture.

YYAnimatedImageView implementation analysis

YYAnimatedImageView internal implementation source code is very interesting and has a lot to share. However, I will not translate the source line by line so as not to write the article in the MarkDown editor. Readers who want to know the details of the implementation are advised to read the source code in combination with the article. I believe that with the idea of the article combing the source code should not seem to have too much difficulty, the article is still focused on the spread of ideas and some worth sharing skills.

Let’s take a brief look at the internal structure of YYAnimatedImageView, so that we can have a general understanding of the structure of YYAnimatedImageView in advance when we analyze the implementation ideas.

@interface YYAnimatedImageView(a){
    @package
    UIImage <YYAnimatedImage> *_curAnimatedImage; ///< current image
    
    dispatch_once_t _onceToken; ///< is used to ensure that initialization code is executed only once
    dispatch_semaphore_t _lock; //< semaphore lock (for _buffer)
    NSOperationQueue *_requestQueue; //< image request queue, serial
    
    CADisplayLink *_link; / / / < frame transformation
    NSTimeInterval _time; ///< the time since the last frame
    
    UIImage *_curFrame; / / / < the current frame
    NSUInteger _curIndex; ///< current frame index
    NSUInteger _totalFrameCount; / / / < the total number of frames
    
    BOOL _loopEnd; ///< is at the end of the loop
    NSUInteger _curLoop; ///< current loop number
    NSUInteger _totalLoop; ///< total number of cycles, 0 means infinity
    
    NSMutableDictionary *_buffer; ///< frame buffer
    BOOL _bufferMiss; If the next frame is not read from the frame buffer, the frame is considered to be lost
    NSUInteger _maxBufferCount; ///< maximum buffer count
    NSInteger _incrBufferCount; ///< currently allowed cache count (will increase gradually)
    
    CGRect _curContentsRect; ///< 针对 YYSpriteSheetImage
    BOOL _curImageHasContentsRect; / / / < image whether class implements animatedImageContentsRectAtIndex: interface
}
@property (nonatomic.readwrite) BOOL currentIsPlayingAnimation;
- (void)calcMaxBufferCount; // Dynamically adjust the maximum buffer limit _maxBufferCount
@end
Copy the code

As you can see, YYAnimatedImageView’s internal structure is much more complex than the attributes exposed in.h, and the CADisplayLink *_link attribute also confirms what we suspected about the runloopMode attribute in.h.

YYAnimatedImageView internal initialization is nothing special, the initialization function will set the image, when the image is determined to change according to the following 4 steps:

  • Change the picture
  • Reset the animation
  • Initialize the animation parameters
  • redraw

Note: This will ensure that YYAnimatedImageView image changes will perform the above steps for the new image initialization of the new animation parameters and redraw, and reset animation implementation will use the above dispatch_once_t _onceToken; To ensure that certain internal variables are created and that the App memory warning and notification into the background are observed only once.

YYAnimatedImageView makes images move by using CADisplayLink *_link; Variable switching frame image, its internal implementation logic can be simply understood as:

  • Extrudes the next frame index based on the current frame index
  • Use the next frame index to retrieve the frame image from the frame buffer
  • If the corresponding frame image is found, it is redrawn
  • If not, add the request operation to the image request queue according to the condition (the frame image data after input into the image buffer).

There are some implementation details worth mentioning.

  • YYAnimatedImageView implementation when_curIndexThe current frame index is added before and after the modification of the codewillChangeValueForKey:didChangeValueForKey:Method to support KVO
  • Frame buffer_bufferAll operations are used_locklocked
  • By sending images to the queue_requestQueuemaxConcurrentOperationCountSet to 1 to make the image request queue serial (Max concurrency 1)
  • The operations added to the image request queue are_YYAnimatedImageViewFetchOperation
  • To avoid usingCADisplayLinkThe possible creation of a circular reference design_YYImageWeakProxy

First take a look at _YYAnimatedImageViewFetchOperation source code:

@interface _YYAnimatedImageViewFetchOperation : NSOperation
@property (nonatomic.weak) YYAnimatedImageView *view;
@property (nonatomic.assign) NSUInteger nextIndex;
@property (nonatomic.strong) UIImage <YYAnimatedImage> *curImage;
@end

@implementation _YYAnimatedImageViewFetchOperation
- (void)main {/ /... }
@end
Copy the code

_YYAnimatedImageViewFetchOperation, inherited from the NSOperation class is a custom action class, the author write content its operations in the main, The code is too long and I feel that Posting will not help readers understand but will affect readers’ understanding of the whole implementation of YYAnimatedImageView because of the one-sided source code implementation (because a lot of source code will make the article a lot of confusion, and will divert readers’ attention to a particular implementation), Here is a brief description of the internal implementation logic of main:

  • Determine the frame buffer size
  • Scan for the next frame and any frames that are within the currently buffered range
  • If a lost frame is found, try to retrieve the frame image and add it to the frame buffer

Well, if you don’t post source code, the details need to be listed (laughs).

  • Operation forviewBuffer operations are also locked
  • Because the operation is placed in the image request queue, there are internal pairsisCancelledDetermine if the action has been canceled (changing the image, stopping the animation, manually changing the current frame, receiving a memory warning, or entering the background of the App, etc.) and then jump out in time
  • For new threads the priority is only inmainMethod scope, so it is recommended to put the implementation of the operation inmainRather than in thestartIf you want to override the start method, you need to be concernedisExecutingisFinishedTwo key paths)

YYAnimatedImageView internal design _YYImageWeakProxy to avoid using NSTimer or CADisplayLink may cause cyclic reference problems, _YYImageWeakProxy internal implementation is relatively simple, Inheriting from NSProxy, you can see the official documentation about NSProxy to learn more.

@interface _YYImageWeakProxy : NSProxy
@property (nonatomic.weak.readonly) id target;
- (instancetype)initWithTarget:(id)target;
+ (instancetype)proxyWithTarget:(id)target;
@end

@implementation _YYImageWeakProxy
// ...
- (id)forwardingTargetForSelector:(SEL)selector {
    return _target;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
    void *null = NULL;
    [invocation setReturnValue:&null];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
    return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
// ...
@end
Copy the code

The source code posted above omits the more basic implementation part, _YYImageWeakProxy internal weak reference to an object target, for _YYImageWeakProxy some basic operations including hash and isEqual all these are transferred to target, And use forwardingTargetForSelector: messages redirects will not be able to response the runtime is redirected to the target response.

Emmmmm.. So the question is, why forward a message when you’re redirecting it to target? Because to avoid circular reference problem using weak references for target, so there is no guarantee that the target during must exist, so forwardingTargetForSelector: Methods may return nil and then borrow the init message in Runtime message forwarding to return null to “swallow” the exception.

Note: Message forwarding incurs more overhead than dynamic method parsing and message redirection.

YYImageCoder

As the codec/decoder of YYImage, YYImageCoder corresponds to the ImageIO. Framework image codec library in iOS. It is because of the existence of YYImageCoder that YYImage can support so many image formats. So YYImageCoder is the underlying core of YYImage.

YYImageCoder internally defines many of the core data structures used in YYImage:

  • YYImageType, all supported image formats are enumerated
  • YYImageDisposeMethod, how to dispose of the region method used by the current frame before rendering the next frame on the canvas
  • YYImageBlendOperation, which specifies how the transparent pixels of the current frame are mixed with the transparent pixels of the previous canvas
  • YYImageFrame, a frame of image data
  • YYImageEncoder, image encoder
  • YYImageDecoder, image decoder
  • UIImage+YYImageCoder, UIImage classification, which provides some convenient methods to use

YYImageFrame is the encapsulation of a frame of image data, which is easy to use in YYImageCoder encoding/decoding process.

YYImageCoder internal image encoder YYImageEncoder and image decoder YYImageDecoder are actually separate, we respectively analyze them below.

YYImageEncoder

Let’s start with YYImageEncoder, which acts as an encoder in YYImageCoder.

@interface YYImageEncoder : NSObject

@property (nonatomic.readonly) YYImageType type; ///< image type
@property (nonatomic) NSUInteger loopCount;       ///< loop times, 0 infinite loop, only suitable for GIF/APNG/WebP format
@property (nonatomic) BOOL lossless;              ///< lossless tag, only for WebP.
@property (nonatomic) CGFloat quality;            //< compression quality, 0.0~1.0, only for JPG/JP2/WebP.

// Disallow init, new encoders (I didn't forget that I mentioned these coding tricks in a later article)
- (instancetype)init UNAVAILABLE_ATTRIBUTE;
+ (instancetype)new UNAVAILABLE_ATTRIBUTE;

// Create an encoder based on the given image type
- (nullable instancetype)initWithType:(YYImageType)type NS_DESIGNATED_INITIALIZER;
// Add an image
- (void)addImage:(UIImage *)image duration:(NSTimeInterval)duration;
// Add image data
- (void)addImageWithData:(NSData *)data duration:(NSTimeInterval)duration;
// Add the file path
- (void)addImageWithFile:(NSString *)path duration:(NSTimeInterval)duration;
// Start image encoding and try to return the encoded data
- (nullable NSData *)encode;
// Encode and save the resulting data to the given path file
- (BOOL)encodeToFile:(NSString *)path;
// A convenient way to encode a single frame of image
+ (nullable NSData *)encodeImage:(UIImage *)image type:(YYImageType)type quality:(CGFloat)quality;
// A convenient way to encode image data from the decoder
+ (nullable NSData *)encodeImageWithDecoder:(YYImageDecoder *)decoder type:(YYImageType)type quality:(CGFloat)quality;

@end
Copy the code

YYImageEncoder (YYImageEncoder, YYImageEncoder, YYImageEncoder, YYImageEncoder, YYImageEncoder, YYImageEncoder)

@implementation YYImageEncoder {
    NSMutableArray *_images; // Add images to encoder (array)
    NSMutableArray *_durations; // The corresponding image frame shows the duration (array)
}
Copy the code

YYImageEncoder implementation ideas

The initialization part of YYImageEncoder is not too complicated, it is only initialized according to the best encoding parameters according to the image type. As for the image encoding work of YYImageEncoder, in fact, the author has made low-level encapsulation according to the image type to be supported and the encoding mode of the corresponding image type, and then selects the corresponding low-level encoding method for execution according to the current image type.

For different picture types of picture encoding format can refer to the extended reading section at the end of this article, combined with the extended reading content refer to YYImage this part of the source code can understand the author for the underlying picture format information structure encapsulation and encoding/decoding operation specific implementation.

For a simple example of using YYImageEncoder, see yyImagecoder.h.

YYImageDecoder

YYImageDecoder plays the role of decoder in YYImageCoder, which corresponds to the above YYImageEncoder. One is responsible for image encoding and the other is responsible for image decoding. However, the implementation of YYImageDecoder is more complex than YYImageEncoder.

@interface YYImageDecoder : NSObject

@property (nullable.nonatomic.readonly) NSData *data;    ///< image data
@property (nonatomic.readonly) YYImageType type;          ///< image data type
@property (nonatomic.readonly) CGFloat scale;             ///< image size
@property (nonatomic.readonly) NSUInteger frameCount;     ///< image frame number
@property (nonatomic.readonly) NSUInteger loopCount;      ///< image loop number, 0 infinite loop
@property (nonatomic.readonly) NSUInteger width;          ///< image canvas width
@property (nonatomic.readonly) NSUInteger height;         ///< image canvas height
@property (nonatomic.readonly.getter=isFinalized) BOOL finalized;

// Create an image decoder
- (instancetype)initWithScale:(CGFloat)scale NS_DESIGNATED_INITIALIZER;
// Update the image with new data increments
- (BOOL)updateData:(nullable NSData *)data final:(BOOL)final;
// It is convenient to create a decoder with a special data
+ (nullable instancetype)decoderWithData:(NSData *)data scale:(CGFloat)scale;
// Decode and return the frame data corresponding to the given index
- (nullable YYImageFrame *)frameAtIndex:(NSUInteger)index decodeForDisplay:(BOOL)decodeForDisplay;
// Returns the frame duration for the given index
- (NSTimeInterval)frameDurationAtIndex:(NSUInteger)index;
// Return the attribute information of the frame corresponding to the given index. Go to the "cgImageProperties.h" file of imageio. framework to learn more
- (nullable NSDictionary *)framePropertiesAtIndex:(NSUInteger)index;
Go to the "cgImageProperties.h" file in imageio. framework to learn more
- (nullable NSDictionary *)imageProperties;

@end
Copy the code

As you can see, YYImageDecoder exposes some properties about decoding images and provides methods for initializing the decoder, decoding the image, and accessing the image frame information. YYImageDecoder implementation is more complex, we then look at its internal variable structure:

@implementation YYImageDecoder {
    pthread_mutex_t _lock; / / recursive locking
    
    BOOL _sourceTypeDetected; // Whether to infer the image source type
    CGImageSourceRef _source; / / the source image
    yy_png_info *_apngSource; // If the image is determined to be YYImageTypePNG, the image source will be updated with APNG
#if YYIMAGE_WEBP_ENABLED
    WebPDemuxer *_webpSource; // If the image is judged to be YYImageTypeWebP, the WebP will update the image source
#endif
    
    UIImageOrientation _orientation; // Draw the direction
    dispatch_semaphore_t _framesLock; // Lock for the image frame
    NSArray *_frames; ///< Array<_YYImageDecoderFrame *>, without image
    BOOL _needBlend; // Whether to mix
    NSUInteger _blendFrameIndex; // Mix from frame index to current frame
    CGContextRef _blendCanvas; // Mix the canvas
}
Copy the code

_YYImageDecoderFrame inherits from YYImageFrame class as the internal frame class of YYImageCoder image decoder YYImageDecoder, which is the data encapsulation of a frame image to provide easy access to the data when encoding/decoding.

YYImageDecoder internal lock option

YYImageDecoder uses two types of locks inside:

  • pthread_mutex_t _lock;
  • dispatch_semaphore_t _framesLock;

Pthread_mutex_t is set as a recursive lock with type PTHREAD_MUTEX_RECURSIVE during decoder initialization.

pthread_mutexattr_t attr;
pthread_mutexattr_init (&attr);
pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init (&_lock, &attr);
pthread_mutexattr_destroy (&attr);
Copy the code

Note: Normally, a thread can only apply for a lock once, and can only release the lock if it has acquired it. Applying for a lock more than once or releasing an unacquired lock will cause a crash. If you apply for a lock again after it has already been acquired, the thread will go to sleep waiting for the lock to be released, making it impossible to release the lock again, resulting in a deadlock.

However, this often happens, for example when a function claims a lock and then recursively calls itself within a critical region. Pthread_mutex supports recursive locking, which allows a thread to recursively apply for a lock by changing the attR type to PTHREAD_MUTEX_RECURSIVE.

The author uses dispatch_SEMaphoRE_T as the lock of the image frame array because dispatch_semaphoRE_T is lighter and the critical operation of the image frame array is faster and does not cause a long time of blocking. In this case dispatch_semaphoRE_t has a performance advantage (Emmmmmm.. Familiar students do not complain, take care of the students behind).

YYImageDecoder in the implementation of ideas

YYImageDecoder will initialize the lock and update the image source data during internal initialization. When the image source is updated, the _updateSource method is called to decode the underlying data structure encapsulated by the author according to the current image type and the corresponding image type decoding rules. After decoding, the corresponding properties are set.

Readers interested in the author’s low-level source code for image data in different formats can refer to the extended reading section at the end of this article.

For some simple examples of using YYImageDecoder, see yyImagecoder.h.

conclusion

  • The article has systematically analyzed YYImage source code, and I hope that readers can have a clear understanding of YYImage’s overall architecture and design ideas after reading this article.
  • This paper interprets three types of images (YYImage, YYFrameImage, and YYSpriteSheetImage) in the Image hierarchy of YYImage respectively, hoping to help readers understand the composition principle of these three types of images and the way of rendering animation.
  • This paper deeply analyzes the internal implementation of YYAnimatedImageView and extracts its design ideas for readers to explore.
  • The author found in his reading source code is worth sharing the implementation of the details combined with the source code carried out alone analysis, I hope you can readers in their usual work to encounter similar situations to be able to some more ideas, packaging project components can use these skills.

The article is written carefully (it is my personal original article, please indicate the source of reprint). If any mistake is found, it will be updated in my personal blog first. If you have any questions, please feel free to contact me on my weibo @lision. Besides, there are many interesting gadgets on my GitHub homepage

Finally, I hope my article can bring you value ~

Further reading

  • Libpng is the official description of the PNG structure
  • APNG’s Wikipedia
  • WebP developer documentation

Supplement ~ I set up a technical exchange wechat group, want to know more friends in it! If you have any questions about the article or encounter some small problems in your work, you can find me or other friends in the group to communicate and discuss. Looking forward to your joining us

Emmmmm.. Because the number of wechat group is over 100, it is not possible to scan the code into the group, so please scan the qr code above to pay attention to the public number into the group.