This commit is contained in:
DDIsFriend
2023-08-23 09:24:40 +08:00
parent 6bd037c5dd
commit 63ca919ed5
494 changed files with 35308 additions and 6623 deletions

22
Pods/YYImage/LICENSE generated Normal file
View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 ibireme <ibireme@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

384
Pods/YYImage/README.md generated Executable file
View File

@@ -0,0 +1,384 @@
YYImage
==============
[![License MIT](https://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://raw.githubusercontent.com/ibireme/YYImage/master/LICENSE)&nbsp;
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)&nbsp;
[![CocoaPods](http://img.shields.io/cocoapods/v/YYImage.svg?style=flat)](http://cocoapods.org/?q= YYImage)&nbsp;
[![CocoaPods](http://img.shields.io/cocoapods/p/YYImage.svg?style=flat)](http://cocoapods.org/?q= YYImage)&nbsp;
[![Support](https://img.shields.io/badge/support-iOS%206%2B%20-blue.svg?style=flat)](https://www.apple.com/nl/ios/)&nbsp;
[![Build Status](https://travis-ci.org/ibireme/YYImage.svg?branch=master)](https://travis-ci.org/ibireme/YYImage)
Image framework for iOS to display/encode/decode animated WebP, APNG, GIF, and more.<br/>
(It's a component of [YYKit](https://github.com/ibireme/YYKit))
![niconiconi~](https://raw.github.com/ibireme/YYImage/master/Demo/YYImageDemo/niconiconi@2x.gif
)
Features
==============
- Display/encode/decode animated image with these types:<br/>&nbsp;&nbsp;&nbsp;&nbsp;WebP, APNG, GIF.
- Display/encode/decode still image with these types:<br/>&nbsp;&nbsp;&nbsp;&nbsp;WebP, PNG, GIF, JPEG, JP2, TIFF, BMP, ICO, ICNS.
- Baseline/progressive/interlaced image decode with these types:<br/>&nbsp;&nbsp;&nbsp;&nbsp;PNG, GIF, JPEG, BMP.
- Display frame based image animation and sprite sheet animation.
- Dynamic memory buffer for lower memory usage.
- Fully compatible with UIImage and UIImageView class.
- Extendable protocol for custom image animation.
- Fully documented.
Usage
==============
###Display animated image
// File: ani@3x.gif
UIImage *image = [YYImage imageNamed:@"ani.gif"];
UIImageView *imageView = [[YYAnimatedImageView alloc] initWithImage:image];
[self.view addSubView:imageView];
###Display frame animation
// Files: frame1.png, frame2.png, frame3.png
NSArray *paths = @[@"/ani/frame1.png", @"/ani/frame2.png", @"/ani/frame3.png"];
NSArray *times = @[@0.1, @0.2, @0.1];
UIImage *image = [YYFrameImage alloc] initWithImagePaths:paths frameDurations:times repeats:YES];
UIImageView *imageView = [YYAnimatedImageView alloc] initWithImage:image];
[self.view addSubView:imageView];
###Display sprite sheet animation
// 8 * 12 sprites in a single sheet image
UIImage *spriteSheet = [UIImage imageNamed:@"sprite-sheet"];
NSMutableArray *contentRects = [NSMutableArray new];
NSMutableArray *durations = [NSMutableArray new];
for (int j = 0; j < 12; j++) {
for (int i = 0; i < 8; i++) {
CGRect rect;
rect.size = CGSizeMake(img.size.width / 8, img.size.height / 12);
rect.origin.x = img.size.width / 8 * i;
rect.origin.y = img.size.height / 12 * j;
[contentRects addObject:[NSValue valueWithCGRect:rect]];
[durations addObject:@(1 / 60.0)];
}
}
YYSpriteSheetImage *sprite;
sprite = [[YYSpriteSheetImage alloc] initWithSpriteSheetImage:img
contentRects:contentRects
frameDurations:durations
loopCount:0];
YYAnimatedImageView *imageView = [YYAnimatedImageView new];
imageView.size = CGSizeMake(img.size.width / 8, img.size.height / 12);
imageView.image = sprite;
[self.view addSubView:imageView];
###Animation control
YYAnimatedImageView *imageView = ...;
// pause:
[imageView stopAnimating];
// play:
[imageView startAnimating];
// set frame index:
imageView.currentAnimatedImageIndex = 12;
// get current status
image.currentIsPlayingAnimation;
###Image decoder
// Decode single frame:
NSData *data = [NSData dataWithContentsOfFile:@"/tmp/image.webp"];
YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:2.0];
UIImage image = [decoder frameAtIndex:0 decodeForDisplay:YES].image;
// Progressive:
NSMutableData *data = [NSMutableData new];
YYImageDecoder *decoder = [[YYImageDecoder alloc] initWithScale:2.0];
while(newDataArrived) {
[data appendData:newData];
[decoder updateData:data final:NO];
if (decoder.frameCount > 0) {
UIImage image = [decoder frameAtIndex:0 decodeForDisplay:YES].image;
// progressive display...
}
}
[decoder updateData:data final:YES];
UIImage image = [decoder frameAtIndex:0 decodeForDisplay:YES].image;
// final display...
###Image encoder
// Encode still image:
YYImageEncoder *jpegEncoder = [[YYImageEncoder alloc] initWithType:YYImageTypeJPEG];
jpegEncoder.quality = 0.9;
[jpegEncoder addImage:image duration:0];
NSData jpegData = [jpegEncoder encode];
// Encode animated image:
YYImageEncoder *webpEncoder = [[YYImageEncoder alloc] initWithType:YYImageTypeWebP];
webpEncoder.loopCount = 5;
[webpEncoder addImage:image0 duration:0.1];
[webpEncoder addImage:image1 duration:0.15];
[webpEncoder addImage:image2 duration:0.2];
NSData webpData = [webpEncoder encode];
###Image type detection
// Get image type from image data
YYImageType type = YYImageDetectType(data);
if (type == YYImageTypePNG) ...
Installation
==============
### CocoaPods
1. Update cocoapods to the latest version.
2. Add `pod 'YYImage'` to your Podfile.
3. Run `pod install` or `pod update`.
4. Import \<YYImage/YYImage.h\>.
5. Notice: it doesn't include WebP subspec by default, if you want to support WebP format, you may add `pod 'YYImage/WebP'` to your Podfile.
### Carthage
1. Add `github "ibireme/YYImage"` to your Cartfile.
2. Run `carthage update --platform ios` and add the framework to your project.
3. Import \<YYImage/YYImage.h\>.
4. Notice: carthage framework doesn't include WebP component, if you want to support WebP format, use CocoaPods or install manually.
### Manually
1. Download all the files in the YYImage subdirectory.
2. Add the source files to your Xcode project.
3. Link with required frameworks:
* UIKit
* CoreFoundation
* QuartzCore
* AssetsLibrary
* ImageIO
* Accelerate
* MobileCoreServices
* libz
4. Import `YYImage.h`.
5. Notice: if you want to support WebP format, you may add `Vendor/WebP.framework`(static library) to your Xcode project.
FAQ
==============
_Q: Why I can't display WebP image?_
A: Make sure you added the `WebP.framework` in your project. You may call `YYImageWebPAvailable()` to check whether the WebP subspec is installed correctly.
_Q: Why I can't play APNG animation?_
A: You should disable the `Compress PNG Files` and `Remove Text Metadata From PNG Files` in your project's build settings. Or you can rename your APNG file's extension name with `apng`.
Documentation
==============
Full API documentation is available on [CocoaDocs](http://cocoadocs.org/docsets/YYImage/).<br/>
You can also install documentation locally using [appledoc](https://github.com/tomaz/appledoc).
Requirements
==============
This library requires `iOS 6.0+` and `Xcode 7.0+`.
License
==============
YYImage is provided under the MIT license. See LICENSE file for details.
<br/><br/>
---
中文介绍
==============
YYImage: 功能强大的 iOS 图像框架。<br/>
(该项目是 [YYKit](https://github.com/ibireme/YYKit) 组件之一)
![niconiconi~](https://raw.github.com/ibireme/YYImage/master/Demo/YYImageDemo/niconiconi@2x.gif
)
特性
==============
- 支持以下类型动画图像的播放/编码/解码:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;WebP, APNG, GIF。
- 支持以下类型静态图像的显示/编码/解码:<br>
&nbsp;&nbsp;&nbsp;&nbsp;WebP, PNG, GIF, JPEG, JP2, TIFF, BMP, ICO, ICNS。
- 支持以下类型图片的渐进式/逐行扫描/隔行扫描解码:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;PNG, GIF, JPEG, BMP。
- 支持多张图片构成的帧动画播放,支持单张图片的 sprite sheet 动画。
- 高效的动态内存缓存管理,以保证高性能低内存的动画播放。
- 完全兼容 UIImage 和 UIImageView使用方便。
- 保留可扩展的接口,以支持自定义动画。
- 每个类和方法都有完善的文档注释。
用法
==============
###显示动画类型的图片
// 文件: ani@3x.gif
UIImage *image = [YYImage imageNamed:@"ani.gif"];
UIImageView *imageView = [[YYAnimatedImageView alloc] initWithImage:image];
[self.view addSubView:imageView];
###播放帧动画
// 文件: frame1.png, frame2.png, frame3.png
NSArray *paths = @[@"/ani/frame1.png", @"/ani/frame2.png", @"/ani/frame3.png"];
NSArray *times = @[@0.1, @0.2, @0.1];
UIImage *image = [YYFrameImage alloc] initWithImagePaths:paths frameDurations:times repeats:YES];
UIImageView *imageView = [YYAnimatedImageView alloc] initWithImage:image];
[self.view addSubView:imageView];
###播放 sprite sheet 动画
// 8 * 12 sprites in a single sheet image
UIImage *spriteSheet = [UIImage imageNamed:@"sprite-sheet"];
NSMutableArray *contentRects = [NSMutableArray new];
NSMutableArray *durations = [NSMutableArray new];
for (int j = 0; j < 12; j++) {
for (int i = 0; i < 8; i++) {
CGRect rect;
rect.size = CGSizeMake(img.size.width / 8, img.size.height / 12);
rect.origin.x = img.size.width / 8 * i;
rect.origin.y = img.size.height / 12 * j;
[contentRects addObject:[NSValue valueWithCGRect:rect]];
[durations addObject:@(1 / 60.0)];
}
}
YYSpriteSheetImage *sprite;
sprite = [[YYSpriteSheetImage alloc] initWithSpriteSheetImage:img
contentRects:contentRects
frameDurations:durations
loopCount:0];
YYAnimatedImageView *imageView = [YYAnimatedImageView new];
imageView.size = CGSizeMake(img.size.width / 8, img.size.height / 12);
imageView.image = sprite;
[self.view addSubView:imageView];
###动画播放控制
YYAnimatedImageView *imageView = ...;
// 暂停:
[imageView stopAnimating];
// 播放:
[imageView startAnimating];
// 设置播放进度:
imageView.currentAnimatedImageIndex = 12;
// 获取播放状态:
image.currentIsPlayingAnimation;
//上面两个属性都支持 KVO
###图片解码
// 解码单帧图片:
NSData *data = [NSData dataWithContentsOfFile:@"/tmp/image.webp"];
YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:2.0];
UIImage image = [decoder frameAtIndex:0 decodeForDisplay:YES].image;
// 渐进式图片解码 (可用于图片下载显示):
NSMutableData *data = [NSMutableData new];
YYImageDecoder *decoder = [[YYImageDecoder alloc] initWithScale:2.0];
while(newDataArrived) {
[data appendData:newData];
[decoder updateData:data final:NO];
if (decoder.frameCount > 0) {
UIImage image = [decoder frameAtIndex:0 decodeForDisplay:YES].image;
// progressive display...
}
}
[decoder updateData:data final:YES];
UIImage image = [decoder frameAtIndex:0 decodeForDisplay:YES].image;
// final display...
###图片编码
// 编码静态图 (支持各种常见图片格式):
YYImageEncoder *jpegEncoder = [[YYImageEncoder alloc] initWithType:YYImageTypeJPEG];
jpegEncoder.quality = 0.9;
[jpegEncoder addImage:image duration:0];
NSData jpegData = [jpegEncoder encode];
// 编码动态图 (支持 GIF/APNG/WebP):
YYImageEncoder *webpEncoder = [[YYImageEncoder alloc] initWithType:YYImageTypeWebP];
webpEncoder.loopCount = 5;
[webpEncoder addImage:image0 duration:0.1];
[webpEncoder addImage:image1 duration:0.15];
[webpEncoder addImage:image2 duration:0.2];
NSData webpData = [webpEncoder encode];
###图片类型探测
// 获取图片类型
YYImageType type = YYImageDetectType(data);
if (type == YYImageTypePNG) ...
安装
==============
### CocoaPods
1. 将 cocoapods 更新至最新版本.
2. 在 Podfile 中添加 `pod 'YYImage'`
3. 执行 `pod install``pod update`
4. 导入 \<YYImage/YYImage.h\>。
5. 注意pod 配置并没有包含 WebP 组件, 如果你需要支持 WebP可以在 Podfile 中添加 `pod 'YYImage/WebP'`
### Carthage
1. 在 Cartfile 中添加 `github "ibireme/YYImage"`
2. 执行 `carthage update --platform ios` 并将生成的 framework 添加到你的工程。
3. 导入 \<YYImage/YYImage.h\>。
4. 注意carthage framework 并没有包含 WebP 组件。如果你需要支持 WebP可以用 CocoaPods 安装,或者手动安装。
### 手动安装
1. 下载 YYImage 文件夹内的所有内容。
2. 将 YYImage 内的源文件添加(拖放)到你的工程。
3. 链接以下 frameworks:
* UIKit
* CoreFoundation
* QuartzCore
* AssetsLibrary
* ImageIO
* Accelerate
* MobileCoreServices
* libz
4. 导入 `YYImage.h`
5. 注意:如果你需要支持 WebP可以将 `Vendor/WebP.framework`(静态库) 加入你的工程。
常见问题
==============
_Q: 为什么我不能显示 WebP 图片_
A: 确保 `WebP.framework` 已经被添加到你的工程内了。你可以调用 `YYImageWebPAvailable()` 来检查一下 WebP 组件是否被正确安装。
_Q: 为什么我不能播放 APNG 动画_
A: 你应该禁用 Build Settings 中的 `Compress PNG Files``Remove Text Metadata From PNG Files`. 或者你也可以把 APNG 文件的扩展名改为`apng`.
文档
==============
你可以在 [CocoaDocs](http://cocoadocs.org/docsets/YYImage/) 查看在线 API 文档,也可以用 [appledoc](https://github.com/tomaz/appledoc) 本地生成文档。
系统要求
==============
该项目最低支持 `iOS 6.0``Xcode 7.0`
许可证
==============
YYImage 使用 MIT 许可证,详情见 LICENSE 文件。
相关链接
==============
[移动端图片格式调研](http://blog.ibireme.com/2015/11/02/mobile_image_benchmark/)<br/>
[iOS 处理图片的一些小 Tip](http://blog.ibireme.com/2015/11/02/ios_image_tips/)

125
Pods/YYImage/YYImage/YYAnimatedImageView.h generated Normal file
View File

@@ -0,0 +1,125 @@
//
// YYAnimatedImageView.h
// YYImage <https://github.com/ibireme/YYImage>
//
// Created by ibireme on 14/10/19.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
An image view for displaying animated image.
@discussion It is a fully compatible `UIImageView` subclass.
If the `image` or `highlightedImage` property adopt to the `YYAnimatedImage` protocol,
then it can be used to play the multi-frame animation. The animation can also be
controlled with the UIImageView methods `-startAnimating`, `-stopAnimating` and `-isAnimating`.
This view request the frame data just in time. When the device has enough free memory,
this view may cache some or all future frames in an inner buffer for lower CPU cost.
Buffer size is dynamically adjusted based on the current state of the device memory.
Sample Code:
// ani@3x.gif
YYImage *image = [YYImage imageNamed:@"ani"];
YYAnimatedImageView *imageView = [YYAnimatedImageView alloc] initWithImage:image];
[view addSubView:imageView];
*/
@interface YYAnimatedImageView : UIImageView
/**
If the image has more than one frame, set this value to `YES` will automatically
play/stop the animation when the view become visible/invisible.
The default value is `YES`.
*/
@property (nonatomic) BOOL autoPlayAnimatedImage;
/**
Index of the currently displayed frame (index from 0).
Set a new value to this property will cause to display the new frame immediately.
If the new value is invalid, this method has no effect.
You can add an observer to this property to observe the playing status.
*/
@property (nonatomic) NSUInteger currentAnimatedImageIndex;
/**
Whether the image view is playing animation currently.
You can add an observer to this property to observe the playing status.
*/
@property (nonatomic, readonly) BOOL currentIsPlayingAnimation;
/**
The animation timer's runloop mode, default is `NSRunLoopCommonModes`.
Set this property to `NSDefaultRunLoopMode` will make the animation pause during
UIScrollView scrolling.
*/
@property (nonatomic, copy) NSString *runloopMode;
/**
The max size (in bytes) for inner frame buffer size, default is 0 (dynamically).
When the device has enough free memory, this view will request and decode some or
all future frame image into an inner buffer. If this property's value is 0, then
the max buffer size will be dynamically adjusted based on the current state of
the device free memory. Otherwise, the buffer size will be limited by this value.
When receive memory warning or app enter background, the buffer will be released
immediately, and may grow back at the right time.
*/
@property (nonatomic) NSUInteger maxBufferSize;
@end
/**
The YYAnimatedImage protocol declares the required methods for animated image
display with YYAnimatedImageView.
Subclass a UIImage and implement this protocol, so that instances of that class
can be set to YYAnimatedImageView.image or YYAnimatedImageView.highlightedImage
to display animation.
See `YYImage` and `YYFrameImage` for example.
*/
@protocol YYAnimatedImage <NSObject>
@required
/// Total animated frame count.
/// It the frame count is less than 1, then the methods below will be ignored.
- (NSUInteger)animatedImageFrameCount;
/// Animation loop count, 0 means infinite looping.
- (NSUInteger)animatedImageLoopCount;
/// Bytes per frame (in memory). It may used to optimize memory buffer size.
- (NSUInteger)animatedImageBytesPerFrame;
/// Returns the frame image from a specified index.
/// This method may be called on background thread.
/// @param index Frame index (zero based).
- (nullable UIImage *)animatedImageFrameAtIndex:(NSUInteger)index;
/// Returns the frames's duration from a specified index.
/// @param index Frame index (zero based).
- (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index;
@optional
/// A rectangle in image coordinates defining the subrectangle of the image that
/// will be displayed. The rectangle should not outside the image's bounds.
/// It may used to display sprite animation with a single image (sprite sheet).
- (CGRect)animatedImageContentsRectAtIndex:(NSUInteger)index;
@end
NS_ASSUME_NONNULL_END

672
Pods/YYImage/YYImage/YYAnimatedImageView.m generated Normal file
View File

@@ -0,0 +1,672 @@
//
// YYAnimatedImageView.m
// YYImage <https://github.com/ibireme/YYImage>
//
// Created by ibireme on 14/10/19.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "YYAnimatedImageView.h"
#import "YYImageCoder.h"
#import <pthread.h>
#import <mach/mach.h>
#define BUFFER_SIZE (10 * 1024 * 1024) // 10MB (minimum memory buffer size)
#define LOCK(...) dispatch_semaphore_wait(self->_lock, DISPATCH_TIME_FOREVER); \
__VA_ARGS__; \
dispatch_semaphore_signal(self->_lock);
#define LOCK_VIEW(...) dispatch_semaphore_wait(view->_lock, DISPATCH_TIME_FOREVER); \
__VA_ARGS__; \
dispatch_semaphore_signal(view->_lock);
static int64_t _YYDeviceMemoryTotal() {
int64_t mem = [[NSProcessInfo processInfo] physicalMemory];
if (mem < -1) mem = -1;
return mem;
}
static int64_t _YYDeviceMemoryFree() {
mach_port_t host_port = mach_host_self();
mach_msg_type_number_t host_size = sizeof(vm_statistics_data_t) / sizeof(integer_t);
vm_size_t page_size;
vm_statistics_data_t vm_stat;
kern_return_t kern;
kern = host_page_size(host_port, &page_size);
if (kern != KERN_SUCCESS) return -1;
kern = host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size);
if (kern != KERN_SUCCESS) return -1;
return vm_stat.free_count * page_size;
}
/**
A proxy used to hold a weak object.
It can be used to avoid retain cycles, such as the target in NSTimer or CADisplayLink.
*/
@interface _YYImageWeakProxy : NSProxy
@property (nonatomic, weak, readonly) id target;
- (instancetype)initWithTarget:(id)target;
+ (instancetype)proxyWithTarget:(id)target;
@end
@implementation _YYImageWeakProxy
- (instancetype)initWithTarget:(id)target {
_target = target;
return self;
}
+ (instancetype)proxyWithTarget:(id)target {
return [[_YYImageWeakProxy alloc] initWithTarget:target];
}
- (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)];
}
- (BOOL)respondsToSelector:(SEL)aSelector {
return [_target respondsToSelector:aSelector];
}
- (BOOL)isEqual:(id)object {
return [_target isEqual:object];
}
- (NSUInteger)hash {
return [_target hash];
}
- (Class)superclass {
return [_target superclass];
}
- (Class)class {
return [_target class];
}
- (BOOL)isKindOfClass:(Class)aClass {
return [_target isKindOfClass:aClass];
}
- (BOOL)isMemberOfClass:(Class)aClass {
return [_target isMemberOfClass:aClass];
}
- (BOOL)conformsToProtocol:(Protocol *)aProtocol {
return [_target conformsToProtocol:aProtocol];
}
- (BOOL)isProxy {
return YES;
}
- (NSString *)description {
return [_target description];
}
- (NSString *)debugDescription {
return [_target debugDescription];
}
@end
typedef NS_ENUM(NSUInteger, YYAnimatedImageType) {
YYAnimatedImageTypeNone = 0,
YYAnimatedImageTypeImage,
YYAnimatedImageTypeHighlightedImage,
YYAnimatedImageTypeImages,
YYAnimatedImageTypeHighlightedImages,
};
@interface YYAnimatedImageView() {
@package
UIImage <YYAnimatedImage> *_curAnimatedImage;
dispatch_once_t _onceToken;
dispatch_semaphore_t _lock; ///< lock for _buffer
NSOperationQueue *_requestQueue; ///< image request queue, serial
CADisplayLink *_link; ///< ticker for change frame
NSTimeInterval _time; ///< time after last frame
UIImage *_curFrame; ///< current frame to display
NSUInteger _curIndex; ///< current frame index (from 0)
NSUInteger _totalFrameCount; ///< total frame count
BOOL _loopEnd; ///< whether the loop is end.
NSUInteger _curLoop; ///< current loop count (from 0)
NSUInteger _totalLoop; ///< total loop count, 0 means infinity
NSMutableDictionary *_buffer; ///< frame buffer
BOOL _bufferMiss; ///< whether miss frame on last opportunity
NSUInteger _maxBufferCount; ///< maximum buffer count
NSInteger _incrBufferCount; ///< current allowed buffer count (will increase by step)
CGRect _curContentsRect;
BOOL _curImageHasContentsRect; ///< image has implementated "animatedImageContentsRectAtIndex:"
}
@property (nonatomic, readwrite) BOOL currentIsPlayingAnimation;
- (void)calcMaxBufferCount;
@end
/// An operation for image fetch
@interface _YYAnimatedImageViewFetchOperation : NSOperation
@property (nonatomic, weak) YYAnimatedImageView *view;
@property (nonatomic, assign) NSUInteger nextIndex;
@property (nonatomic, strong) UIImage <YYAnimatedImage> *curImage;
@end
@implementation _YYAnimatedImageViewFetchOperation
- (void)main {
__strong YYAnimatedImageView *view = _view;
if (!view) return;
if ([self isCancelled]) return;
view->_incrBufferCount++;
if (view->_incrBufferCount == 0) [view calcMaxBufferCount];
if (view->_incrBufferCount > (NSInteger)view->_maxBufferCount) {
view->_incrBufferCount = view->_maxBufferCount;
}
NSUInteger idx = _nextIndex;
NSUInteger max = view->_incrBufferCount < 1 ? 1 : view->_incrBufferCount;
NSUInteger total = view->_totalFrameCount;
view = nil;
for (int i = 0; i < max; i++, idx++) {
@autoreleasepool {
if (idx >= total) idx = 0;
if ([self isCancelled]) break;
__strong YYAnimatedImageView *view = _view;
if (!view) break;
LOCK_VIEW(BOOL miss = (view->_buffer[@(idx)] == nil));
if (miss) {
UIImage *img = [_curImage animatedImageFrameAtIndex:idx];
img = img.yy_imageByDecoded;
if ([self isCancelled]) break;
LOCK_VIEW(view->_buffer[@(idx)] = img ? img : [NSNull null]);
view = nil;
}
}
}
}
@end
@implementation YYAnimatedImageView
- (instancetype)init {
self = [super init];
_runloopMode = NSRunLoopCommonModes;
_autoPlayAnimatedImage = YES;
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
_runloopMode = NSRunLoopCommonModes;
_autoPlayAnimatedImage = YES;
return self;
}
- (instancetype)initWithImage:(UIImage *)image {
self = [super init];
_runloopMode = NSRunLoopCommonModes;
_autoPlayAnimatedImage = YES;
self.frame = (CGRect) {CGPointZero, image.size };
self.image = image;
return self;
}
- (instancetype)initWithImage:(UIImage *)image highlightedImage:(UIImage *)highlightedImage {
self = [super init];
_runloopMode = NSRunLoopCommonModes;
_autoPlayAnimatedImage = YES;
CGSize size = image ? image.size : highlightedImage.size;
self.frame = (CGRect) {CGPointZero, size };
self.image = image;
self.highlightedImage = highlightedImage;
return self;
}
// init the animated params.
- (void)resetAnimated {
dispatch_once(&_onceToken, ^{
_lock = dispatch_semaphore_create(1);
_buffer = [NSMutableDictionary new];
_requestQueue = [[NSOperationQueue alloc] init];
_requestQueue.maxConcurrentOperationCount = 1;
_link = [CADisplayLink displayLinkWithTarget:[_YYImageWeakProxy proxyWithTarget:self] selector:@selector(step:)];
if (_runloopMode) {
[_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:_runloopMode];
}
_link.paused = YES;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
});
[_requestQueue cancelAllOperations];
LOCK(
if (_buffer.count) {
NSMutableDictionary *holder = _buffer;
_buffer = [NSMutableDictionary new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// Capture the dictionary to global queue,
// release these images in background to avoid blocking UI thread.
[holder class];
});
}
);
_link.paused = YES;
_time = 0;
if (_curIndex != 0) {
[self willChangeValueForKey:@"currentAnimatedImageIndex"];
_curIndex = 0;
[self didChangeValueForKey:@"currentAnimatedImageIndex"];
}
_curAnimatedImage = nil;
_curFrame = nil;
_curLoop = 0;
_totalLoop = 0;
_totalFrameCount = 1;
_loopEnd = NO;
_bufferMiss = NO;
_incrBufferCount = 0;
}
- (void)setImage:(UIImage *)image {
if (self.image == image) return;
[self setImage:image withType:YYAnimatedImageTypeImage];
}
- (void)setHighlightedImage:(UIImage *)highlightedImage {
if (self.highlightedImage == highlightedImage) return;
[self setImage:highlightedImage withType:YYAnimatedImageTypeHighlightedImage];
}
- (void)setAnimationImages:(NSArray *)animationImages {
if (self.animationImages == animationImages) return;
[self setImage:animationImages withType:YYAnimatedImageTypeImages];
}
- (void)setHighlightedAnimationImages:(NSArray *)highlightedAnimationImages {
if (self.highlightedAnimationImages == highlightedAnimationImages) return;
[self setImage:highlightedAnimationImages withType:YYAnimatedImageTypeHighlightedImages];
}
- (void)setHighlighted:(BOOL)highlighted {
[super setHighlighted:highlighted];
if (_link) [self resetAnimated];
[self imageChanged];
}
- (id)imageForType:(YYAnimatedImageType)type {
switch (type) {
case YYAnimatedImageTypeNone: return nil;
case YYAnimatedImageTypeImage: return self.image;
case YYAnimatedImageTypeHighlightedImage: return self.highlightedImage;
case YYAnimatedImageTypeImages: return self.animationImages;
case YYAnimatedImageTypeHighlightedImages: return self.highlightedAnimationImages;
}
return nil;
}
- (YYAnimatedImageType)currentImageType {
YYAnimatedImageType curType = YYAnimatedImageTypeNone;
if (self.highlighted) {
if (self.highlightedAnimationImages.count) curType = YYAnimatedImageTypeHighlightedImages;
else if (self.highlightedImage) curType = YYAnimatedImageTypeHighlightedImage;
}
if (curType == YYAnimatedImageTypeNone) {
if (self.animationImages.count) curType = YYAnimatedImageTypeImages;
else if (self.image) curType = YYAnimatedImageTypeImage;
}
return curType;
}
- (void)setImage:(id)image withType:(YYAnimatedImageType)type {
[self stopAnimating];
if (_link) [self resetAnimated];
_curFrame = nil;
switch (type) {
case YYAnimatedImageTypeNone: break;
case YYAnimatedImageTypeImage: super.image = image; break;
case YYAnimatedImageTypeHighlightedImage: super.highlightedImage = image; break;
case YYAnimatedImageTypeImages: super.animationImages = image; break;
case YYAnimatedImageTypeHighlightedImages: super.highlightedAnimationImages = image; break;
}
[self imageChanged];
}
- (void)imageChanged {
YYAnimatedImageType newType = [self currentImageType];
id newVisibleImage = [self imageForType:newType];
NSUInteger newImageFrameCount = 0;
BOOL hasContentsRect = NO;
if ([newVisibleImage isKindOfClass:[UIImage class]] &&
[newVisibleImage conformsToProtocol:@protocol(YYAnimatedImage)]) {
newImageFrameCount = ((UIImage<YYAnimatedImage> *) newVisibleImage).animatedImageFrameCount;
if (newImageFrameCount > 1) {
hasContentsRect = [((UIImage<YYAnimatedImage> *) newVisibleImage) respondsToSelector:@selector(animatedImageContentsRectAtIndex:)];
}
}
if (!hasContentsRect && _curImageHasContentsRect) {
if (!CGRectEqualToRect(self.layer.contentsRect, CGRectMake(0, 0, 1, 1)) ) {
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.layer.contentsRect = CGRectMake(0, 0, 1, 1);
[CATransaction commit];
}
}
_curImageHasContentsRect = hasContentsRect;
if (hasContentsRect) {
CGRect rect = [((UIImage<YYAnimatedImage> *) newVisibleImage) animatedImageContentsRectAtIndex:0];
[self setContentsRect:rect forImage:newVisibleImage];
}
if (newImageFrameCount > 1) {
[self resetAnimated];
_curAnimatedImage = newVisibleImage;
_curFrame = newVisibleImage;
_totalLoop = _curAnimatedImage.animatedImageLoopCount;
_totalFrameCount = _curAnimatedImage.animatedImageFrameCount;
[self calcMaxBufferCount];
}
[self setNeedsDisplay];
[self didMoved];
}
// dynamically adjust buffer size for current memory.
- (void)calcMaxBufferCount {
int64_t bytes = (int64_t)_curAnimatedImage.animatedImageBytesPerFrame;
if (bytes == 0) bytes = 1024;
int64_t total = _YYDeviceMemoryTotal();
int64_t free = _YYDeviceMemoryFree();
int64_t max = MIN(total * 0.2, free * 0.6);
max = MAX(max, BUFFER_SIZE);
if (_maxBufferSize) max = max > _maxBufferSize ? _maxBufferSize : max;
double maxBufferCount = (double)max / (double)bytes;
if (maxBufferCount < 1) maxBufferCount = 1;
else if (maxBufferCount > 512) maxBufferCount = 512;
_maxBufferCount = maxBufferCount;
}
- (void)dealloc {
[_requestQueue cancelAllOperations];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
[_link invalidate];
}
- (BOOL)isAnimating {
return self.currentIsPlayingAnimation;
}
- (void)stopAnimating {
[super stopAnimating];
[_requestQueue cancelAllOperations];
_link.paused = YES;
self.currentIsPlayingAnimation = NO;
}
- (void)startAnimating {
YYAnimatedImageType type = [self currentImageType];
if (type == YYAnimatedImageTypeImages || type == YYAnimatedImageTypeHighlightedImages) {
NSArray *images = [self imageForType:type];
if (images.count > 0) {
[super startAnimating];
self.currentIsPlayingAnimation = YES;
}
} else {
if (_curAnimatedImage && _link.paused) {
_curLoop = 0;
_loopEnd = NO;
_link.paused = NO;
self.currentIsPlayingAnimation = YES;
}
}
}
- (void)didReceiveMemoryWarning:(NSNotification *)notification {
[_requestQueue cancelAllOperations];
[_requestQueue addOperationWithBlock: ^{
_incrBufferCount = -60 - (int)(arc4random() % 120); // about 1~3 seconds to grow back..
NSNumber *next = @((_curIndex + 1) % _totalFrameCount);
LOCK(
NSArray * keys = _buffer.allKeys;
for (NSNumber * key in keys) {
if (![key isEqualToNumber:next]) { // keep the next frame for smoothly animation
[_buffer removeObjectForKey:key];
}
}
)//LOCK
}];
}
- (void)didEnterBackground:(NSNotification *)notification {
[_requestQueue cancelAllOperations];
NSNumber *next = @((_curIndex + 1) % _totalFrameCount);
LOCK(
NSArray * keys = _buffer.allKeys;
for (NSNumber * key in keys) {
if (![key isEqualToNumber:next]) { // keep the next frame for smoothly animation
[_buffer removeObjectForKey:key];
}
}
)//LOCK
}
- (void)step:(CADisplayLink *)link {
UIImage <YYAnimatedImage> *image = _curAnimatedImage;
NSMutableDictionary *buffer = _buffer;
UIImage *bufferedImage = nil;
NSUInteger nextIndex = (_curIndex + 1) % _totalFrameCount;
BOOL bufferIsFull = NO;
if (!image) return;
if (_loopEnd) { // view will keep in last frame
[self stopAnimating];
return;
}
NSTimeInterval delay = 0;
if (!_bufferMiss) {
_time += link.duration;
delay = [image animatedImageDurationAtIndex:_curIndex];
if (_time < delay) return;
_time -= delay;
if (nextIndex == 0) {
_curLoop++;
if (_curLoop >= _totalLoop && _totalLoop != 0) {
_loopEnd = YES;
[self stopAnimating];
[self.layer setNeedsDisplay]; // let system call `displayLayer:` before runloop sleep
return; // stop at last frame
}
}
delay = [image animatedImageDurationAtIndex:nextIndex];
if (_time > delay) _time = delay; // do not jump over frame
}
LOCK(
bufferedImage = buffer[@(nextIndex)];
if (bufferedImage) {
if ((int)_incrBufferCount < _totalFrameCount) {
[buffer removeObjectForKey:@(nextIndex)];
}
[self willChangeValueForKey:@"currentAnimatedImageIndex"];
_curIndex = nextIndex;
[self didChangeValueForKey:@"currentAnimatedImageIndex"];
_curFrame = bufferedImage == (id)[NSNull null] ? nil : bufferedImage;
if (_curImageHasContentsRect) {
_curContentsRect = [image animatedImageContentsRectAtIndex:_curIndex];
[self setContentsRect:_curContentsRect forImage:_curFrame];
}
nextIndex = (_curIndex + 1) % _totalFrameCount;
_bufferMiss = NO;
if (buffer.count == _totalFrameCount) {
bufferIsFull = YES;
}
} else {
_bufferMiss = YES;
}
)//LOCK
if (!_bufferMiss) {
[self.layer setNeedsDisplay]; // let system call `displayLayer:` before runloop sleep
}
if (!bufferIsFull && _requestQueue.operationCount == 0) { // if some work not finished, wait for next opportunity
_YYAnimatedImageViewFetchOperation *operation = [_YYAnimatedImageViewFetchOperation new];
operation.view = self;
operation.nextIndex = nextIndex;
operation.curImage = image;
[_requestQueue addOperation:operation];
}
}
- (void)displayLayer:(CALayer *)layer {
if (_curFrame) {
layer.contents = (__bridge id)_curFrame.CGImage;
}
}
- (void)setContentsRect:(CGRect)rect forImage:(UIImage *)image{
CGRect layerRect = CGRectMake(0, 0, 1, 1);
if (image) {
CGSize imageSize = image.size;
if (imageSize.width > 0.01 && imageSize.height > 0.01) {
layerRect.origin.x = rect.origin.x / imageSize.width;
layerRect.origin.y = rect.origin.y / imageSize.height;
layerRect.size.width = rect.size.width / imageSize.width;
layerRect.size.height = rect.size.height / imageSize.height;
layerRect = CGRectIntersection(layerRect, CGRectMake(0, 0, 1, 1));
if (CGRectIsNull(layerRect) || CGRectIsEmpty(layerRect)) {
layerRect = CGRectMake(0, 0, 1, 1);
}
}
}
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.layer.contentsRect = layerRect;
[CATransaction commit];
}
- (void)didMoved {
if (self.autoPlayAnimatedImage) {
if(self.superview && self.window) {
[self startAnimating];
} else {
[self stopAnimating];
}
}
}
- (void)didMoveToWindow {
[super didMoveToWindow];
[self didMoved];
}
- (void)didMoveToSuperview {
[super didMoveToSuperview];
[self didMoved];
}
- (void)setCurrentAnimatedImageIndex:(NSUInteger)currentAnimatedImageIndex {
if (!_curAnimatedImage) return;
if (currentAnimatedImageIndex >= _curAnimatedImage.animatedImageFrameCount) return;
if (_curIndex == currentAnimatedImageIndex) return;
void (^block)() = ^{
LOCK(
[_requestQueue cancelAllOperations];
[_buffer removeAllObjects];
[self willChangeValueForKey:@"currentAnimatedImageIndex"];
_curIndex = currentAnimatedImageIndex;
[self didChangeValueForKey:@"currentAnimatedImageIndex"];
_curFrame = [_curAnimatedImage animatedImageFrameAtIndex:_curIndex];
if (_curImageHasContentsRect) {
_curContentsRect = [_curAnimatedImage animatedImageContentsRectAtIndex:_curIndex];
}
_time = 0;
_loopEnd = NO;
_bufferMiss = NO;
[self.layer setNeedsDisplay];
)//LOCK
};
if (pthread_main_np()) {
block();
} else {
dispatch_async(dispatch_get_main_queue(), block);
}
}
- (NSUInteger)currentAnimatedImageIndex {
return _curIndex;
}
- (void)setRunloopMode:(NSString *)runloopMode {
if ([_runloopMode isEqual:runloopMode]) return;
if (_link) {
if (_runloopMode) {
[_link removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:_runloopMode];
}
if (runloopMode.length) {
[_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:runloopMode];
}
}
_runloopMode = runloopMode.copy;
}
#pragma mark - Override NSObject(NSKeyValueObservingCustomization)
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
if ([key isEqualToString:@"currentAnimatedImageIndex"]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
#pragma mark - NSCoding
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
_runloopMode = [aDecoder decodeObjectForKey:@"runloopMode"];
if (_runloopMode.length == 0) _runloopMode = NSRunLoopCommonModes;
if ([aDecoder containsValueForKey:@"autoPlayAnimatedImage"]) {
_autoPlayAnimatedImage = [aDecoder decodeBoolForKey:@"autoPlayAnimatedImage"];
} else {
_autoPlayAnimatedImage = YES;
}
UIImage *image = [aDecoder decodeObjectForKey:@"YYAnimatedImage"];
UIImage *highlightedImage = [aDecoder decodeObjectForKey:@"YYHighlightedAnimatedImage"];
if (image) {
self.image = image;
[self setImage:image withType:YYAnimatedImageTypeImage];
}
if (highlightedImage) {
self.highlightedImage = highlightedImage;
[self setImage:highlightedImage withType:YYAnimatedImageTypeHighlightedImage];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[super encodeWithCoder:aCoder];
[aCoder encodeObject:_runloopMode forKey:@"runloopMode"];
[aCoder encodeBool:_autoPlayAnimatedImage forKey:@"autoPlayAnimatedImage"];
BOOL ani, multi;
ani = [self.image conformsToProtocol:@protocol(YYAnimatedImage)];
multi = (ani && ((UIImage <YYAnimatedImage> *)self.image).animatedImageFrameCount > 1);
if (multi) [aCoder encodeObject:self.image forKey:@"YYAnimatedImage"];
ani = [self.highlightedImage conformsToProtocol:@protocol(YYAnimatedImage)];
multi = (ani && ((UIImage <YYAnimatedImage> *)self.highlightedImage).animatedImageFrameCount > 1);
if (multi) [aCoder encodeObject:self.highlightedImage forKey:@"YYHighlightedAnimatedImage"];
}
@end

109
Pods/YYImage/YYImage/YYFrameImage.h generated Normal file
View File

@@ -0,0 +1,109 @@
//
// YYFrameImage.h
// YYImage <https://github.com/ibireme/YYImage>
//
// Created by ibireme on 14/12/9.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
#if __has_include(<YYImage/YYImage.h>)
#import <YYImage/YYAnimatedImageView.h>
#elif __has_include(<YYWebImage/YYImage.h>)
#import <YYWebImage/YYAnimatedImageView.h>
#else
#import "YYAnimatedImageView.h"
#endif
NS_ASSUME_NONNULL_BEGIN
/**
An image to display frame-based animation.
@discussion It is a fully compatible `UIImage` subclass.
It only support system image format such as png and jpeg.
The animation can be played by YYAnimatedImageView.
Sample Code:
NSArray *paths = @[@"/ani/frame1.png", @"/ani/frame2.png", @"/ani/frame3.png"];
NSArray *times = @[@0.1, @0.2, @0.1];
YYFrameImage *image = [YYFrameImage alloc] initWithImagePaths:paths frameDurations:times repeats:YES];
YYAnimatedImageView *imageView = [YYAnimatedImageView alloc] initWithImage:image];
[view addSubView:imageView];
*/
@interface YYFrameImage : UIImage <YYAnimatedImage>
/**
Create a frame animated image from files.
@param paths An array of NSString objects, contains the full or
partial path to each image file.
e.g. @[@"/ani/1.png",@"/ani/2.png",@"/ani/3.png"]
@param oneFrameDuration The duration (in seconds) per frame.
@param loopCount The animation loop count, 0 means infinite.
@return An initialized YYFrameImage object, or nil when an error occurs.
*/
- (nullable instancetype)initWithImagePaths:(NSArray<NSString *> *)paths
oneFrameDuration:(NSTimeInterval)oneFrameDuration
loopCount:(NSUInteger)loopCount;
/**
Create a frame animated image from files.
@param paths An array of NSString objects, contains the full or
partial path to each image file.
e.g. @[@"/ani/frame1.png",@"/ani/frame2.png",@"/ani/frame3.png"]
@param frameDurations An array of NSNumber objects, contains the duration (in seconds) per frame.
e.g. @[@0.1, @0.2, @0.3];
@param loopCount The animation loop count, 0 means infinite.
@return An initialized YYFrameImage object, or nil when an error occurs.
*/
- (nullable instancetype)initWithImagePaths:(NSArray<NSString *> *)paths
frameDurations:(NSArray<NSNumber *> *)frameDurations
loopCount:(NSUInteger)loopCount;
/**
Create a frame animated image from an array of data.
@param dataArray An array of NSData objects.
@param oneFrameDuration The duration (in seconds) per frame.
@param loopCount The animation loop count, 0 means infinite.
@return An initialized YYFrameImage object, or nil when an error occurs.
*/
- (nullable instancetype)initWithImageDataArray:(NSArray<NSData *> *)dataArray
oneFrameDuration:(NSTimeInterval)oneFrameDuration
loopCount:(NSUInteger)loopCount;
/**
Create a frame animated image from an array of data.
@param dataArray An array of NSData objects.
@param frameDurations An array of NSNumber objects, contains the duration (in seconds) per frame.
e.g. @[@0.1, @0.2, @0.3];
@param loopCount The animation loop count, 0 means infinite.
@return An initialized YYFrameImage object, or nil when an error occurs.
*/
- (nullable instancetype)initWithImageDataArray:(NSArray<NSData *> *)dataArray
frameDurations:(NSArray *)frameDurations
loopCount:(NSUInteger)loopCount;
@end
NS_ASSUME_NONNULL_END

150
Pods/YYImage/YYImage/YYFrameImage.m generated Normal file
View File

@@ -0,0 +1,150 @@
//
// YYFrameImage.m
// YYImage <https://github.com/ibireme/YYImage>
//
// Created by ibireme on 14/12/9.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "YYFrameImage.h"
#import "YYImageCoder.h"
/**
Return the path scale.
e.g.
<table>
<tr><th>Path </th><th>Scale </th></tr>
<tr><td>"icon.png" </td><td>1 </td></tr>
<tr><td>"icon@2x.png" </td><td>2 </td></tr>
<tr><td>"icon@2.5x.png" </td><td>2.5 </td></tr>
<tr><td>"icon@2x" </td><td>1 </td></tr>
<tr><td>"icon@2x..png" </td><td>1 </td></tr>
<tr><td>"icon@2x.png/" </td><td>1 </td></tr>
</table>
*/
static CGFloat _NSStringPathScale(NSString *string) {
if (string.length == 0 || [string hasSuffix:@"/"]) return 1;
NSString *name = string.stringByDeletingPathExtension;
__block CGFloat scale = 1;
NSRegularExpression *pattern = [NSRegularExpression regularExpressionWithPattern:@"@[0-9]+\\.?[0-9]*x$" options:NSRegularExpressionAnchorsMatchLines error:nil];
[pattern enumerateMatchesInString:name options:kNilOptions range:NSMakeRange(0, name.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
if (result.range.location >= 3) {
scale = [string substringWithRange:NSMakeRange(result.range.location + 1, result.range.length - 2)].doubleValue;
}
}];
return scale;
}
@implementation YYFrameImage {
NSUInteger _loopCount;
NSUInteger _oneFrameBytes;
NSArray *_imagePaths;
NSArray *_imageDatas;
NSArray *_frameDurations;
}
- (instancetype)initWithImagePaths:(NSArray *)paths oneFrameDuration:(NSTimeInterval)oneFrameDuration loopCount:(NSUInteger)loopCount {
NSMutableArray *durations = [NSMutableArray new];
for (int i = 0, max = (int)paths.count; i < max; i++) {
[durations addObject:@(oneFrameDuration)];
}
return [self initWithImagePaths:paths frameDurations:durations loopCount:loopCount];
}
- (instancetype)initWithImagePaths:(NSArray *)paths frameDurations:(NSArray *)frameDurations loopCount:(NSUInteger)loopCount {
if (paths.count == 0) return nil;
if (paths.count != frameDurations.count) return nil;
NSString *firstPath = paths[0];
NSData *firstData = [NSData dataWithContentsOfFile:firstPath];
CGFloat scale = _NSStringPathScale(firstPath);
UIImage *firstCG = [[[UIImage alloc] initWithData:firstData] yy_imageByDecoded];
self = [self initWithCGImage:firstCG.CGImage scale:scale orientation:UIImageOrientationUp];
if (!self) return nil;
long frameByte = CGImageGetBytesPerRow(firstCG.CGImage) * CGImageGetHeight(firstCG.CGImage);
_oneFrameBytes = (NSUInteger)frameByte;
_imagePaths = paths.copy;
_frameDurations = frameDurations.copy;
_loopCount = loopCount;
return self;
}
- (instancetype)initWithImageDataArray:(NSArray *)dataArray oneFrameDuration:(NSTimeInterval)oneFrameDuration loopCount:(NSUInteger)loopCount {
NSMutableArray *durations = [NSMutableArray new];
for (int i = 0, max = (int)dataArray.count; i < max; i++) {
[durations addObject:@(oneFrameDuration)];
}
return [self initWithImageDataArray:dataArray frameDurations:durations loopCount:loopCount];
}
- (instancetype)initWithImageDataArray:(NSArray *)dataArray frameDurations:(NSArray *)frameDurations loopCount:(NSUInteger)loopCount {
if (dataArray.count == 0) return nil;
if (dataArray.count != frameDurations.count) return nil;
NSData *firstData = dataArray[0];
CGFloat scale = [UIScreen mainScreen].scale;
UIImage *firstCG = [[[UIImage alloc] initWithData:firstData] yy_imageByDecoded];
self = [self initWithCGImage:firstCG.CGImage scale:scale orientation:UIImageOrientationUp];
if (!self) return nil;
long frameByte = CGImageGetBytesPerRow(firstCG.CGImage) * CGImageGetHeight(firstCG.CGImage);
_oneFrameBytes = (NSUInteger)frameByte;
_imageDatas = dataArray.copy;
_frameDurations = frameDurations.copy;
_loopCount = loopCount;
return self;
}
#pragma mark - YYAnimtedImage
- (NSUInteger)animatedImageFrameCount {
if (_imagePaths) {
return _imagePaths.count;
} else if (_imageDatas) {
return _imageDatas.count;
} else {
return 1;
}
}
- (NSUInteger)animatedImageLoopCount {
return _loopCount;
}
- (NSUInteger)animatedImageBytesPerFrame {
return _oneFrameBytes;
}
- (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {
if (_imagePaths) {
if (index >= _imagePaths.count) return nil;
NSString *path = _imagePaths[index];
CGFloat scale = _NSStringPathScale(path);
NSData *data = [NSData dataWithContentsOfFile:path];
return [[UIImage imageWithData:data scale:scale] yy_imageByDecoded];
} else if (_imageDatas) {
if (index >= _imageDatas.count) return nil;
NSData *data = _imageDatas[index];
return [[UIImage imageWithData:data scale:[UIScreen mainScreen].scale] yy_imageByDecoded];
} else {
return index == 0 ? self : nil;
}
}
- (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index {
if (index >= _frameDurations.count) return 0;
NSNumber *num = _frameDurations[index];
return [num doubleValue];
}
@end

92
Pods/YYImage/YYImage/YYImage.h generated Normal file
View File

@@ -0,0 +1,92 @@
//
// YYImage.h
// YYImage <https://github.com/ibireme/YYImage>
//
// Created by ibireme on 14/10/20.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
#if __has_include(<YYImage/YYImage.h>)
FOUNDATION_EXPORT double YYImageVersionNumber;
FOUNDATION_EXPORT const unsigned char YYImageVersionString[];
#import <YYImage/YYFrameImage.h>
#import <YYImage/YYSpriteSheetImage.h>
#import <YYImage/YYImageCoder.h>
#import <YYImage/YYAnimatedImageView.h>
#elif __has_include(<YYWebImage/YYImage.h>)
#import <YYWebImage/YYFrameImage.h>
#import <YYWebImage/YYSpriteSheetImage.h>
#import <YYWebImage/YYImageCoder.h>
#import <YYWebImage/YYAnimatedImageView.h>
#else
#import "YYFrameImage.h"
#import "YYSpriteSheetImage.h"
#import "YYImageCoder.h"
#import "YYAnimatedImageView.h"
#endif
NS_ASSUME_NONNULL_BEGIN
/**
A YYImage object is a high-level way to display animated image data.
@discussion It is a fully compatible `UIImage` subclass. It extends the UIImage
to support animated WebP, APNG and GIF format image data decoding. It also
support NSCoding protocol to archive and unarchive multi-frame image data.
If the image is created from multi-frame image data, and you want to play the
animation, try replace UIImageView with `YYAnimatedImageView`.
Sample Code:
// animation@3x.webp
YYImage *image = [YYImage imageNamed:@"animation.webp"];
YYAnimatedImageView *imageView = [YYAnimatedImageView alloc] initWithImage:image];
[view addSubView:imageView];
*/
@interface YYImage : UIImage <YYAnimatedImage>
+ (nullable YYImage *)imageNamed:(NSString *)name; // no cache!
+ (nullable YYImage *)imageWithContentsOfFile:(NSString *)path;
+ (nullable YYImage *)imageWithData:(NSData *)data;
+ (nullable YYImage *)imageWithData:(NSData *)data scale:(CGFloat)scale;
/**
If the image is created from data or file, then the value indicates the data type.
*/
@property (nonatomic, readonly) YYImageType animatedImageType;
/**
If the image is created from animated image data (multi-frame GIF/APNG/WebP),
this property stores the original image data.
*/
@property (nullable, nonatomic, readonly) NSData *animatedImageData;
/**
The total memory usage (in bytes) if all frame images was loaded into memory.
The value is 0 if the image is not created from a multi-frame image data.
*/
@property (nonatomic, readonly) NSUInteger animatedImageMemorySize;
/**
Preload all frame image to memory.
@discussion Set this property to `YES` will block the calling thread to decode
all animation frame image to memory, set to `NO` will release the preloaded frames.
If the image is shared by lots of image views (such as emoticon), preload all
frames will reduce the CPU cost.
See `animatedImageMemorySize` for memory cost.
*/
@property (nonatomic) BOOL preloadAllAnimatedImageFrames;
@end
NS_ASSUME_NONNULL_END

254
Pods/YYImage/YYImage/YYImage.m generated Normal file
View File

@@ -0,0 +1,254 @@
//
// YYImage.m
// YYImage <https://github.com/ibireme/YYImage>
//
// Created by ibireme on 14/10/20.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "YYImage.h"
/**
An array of NSNumber objects, shows the best order for path scale search.
e.g. iPhone3GS:@[@1,@2,@3] iPhone5:@[@2,@3,@1] iPhone6 Plus:@[@3,@2,@1]
*/
static NSArray *_NSBundlePreferredScales() {
static NSArray *scales;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
CGFloat screenScale = [UIScreen mainScreen].scale;
if (screenScale <= 1) {
scales = @[@1,@2,@3];
} else if (screenScale <= 2) {
scales = @[@2,@3,@1];
} else {
scales = @[@3,@2,@1];
}
});
return scales;
}
/**
Add scale modifier to the file name (without path extension),
From @"name" to @"name@2x".
e.g.
<table>
<tr><th>Before </th><th>After(scale:2)</th></tr>
<tr><td>"icon" </td><td>"icon@2x" </td></tr>
<tr><td>"icon " </td><td>"icon @2x" </td></tr>
<tr><td>"icon.top" </td><td>"icon.top@2x" </td></tr>
<tr><td>"/p/name" </td><td>"/p/name@2x" </td></tr>
<tr><td>"/path/" </td><td>"/path/" </td></tr>
</table>
@param scale Resource scale.
@return String by add scale modifier, or just return if it's not end with file name.
*/
static NSString *_NSStringByAppendingNameScale(NSString *string, CGFloat scale) {
if (!string) return nil;
if (fabs(scale - 1) <= __FLT_EPSILON__ || string.length == 0 || [string hasSuffix:@"/"]) return string.copy;
return [string stringByAppendingFormat:@"@%@x", @(scale)];
}
/**
Return the path scale.
e.g.
<table>
<tr><th>Path </th><th>Scale </th></tr>
<tr><td>"icon.png" </td><td>1 </td></tr>
<tr><td>"icon@2x.png" </td><td>2 </td></tr>
<tr><td>"icon@2.5x.png" </td><td>2.5 </td></tr>
<tr><td>"icon@2x" </td><td>1 </td></tr>
<tr><td>"icon@2x..png" </td><td>1 </td></tr>
<tr><td>"icon@2x.png/" </td><td>1 </td></tr>
</table>
*/
static CGFloat _NSStringPathScale(NSString *string) {
if (string.length == 0 || [string hasSuffix:@"/"]) return 1;
NSString *name = string.stringByDeletingPathExtension;
__block CGFloat scale = 1;
NSRegularExpression *pattern = [NSRegularExpression regularExpressionWithPattern:@"@[0-9]+\\.?[0-9]*x$" options:NSRegularExpressionAnchorsMatchLines error:nil];
[pattern enumerateMatchesInString:name options:kNilOptions range:NSMakeRange(0, name.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
if (result.range.location >= 3) {
scale = [string substringWithRange:NSMakeRange(result.range.location + 1, result.range.length - 2)].doubleValue;
}
}];
return scale;
}
@implementation YYImage {
YYImageDecoder *_decoder;
NSArray *_preloadedFrames;
dispatch_semaphore_t _preloadedLock;
NSUInteger _bytesPerFrame;
}
+ (YYImage *)imageNamed:(NSString *)name {
if (name.length == 0) return nil;
if ([name hasSuffix:@"/"]) return nil;
NSString *res = name.stringByDeletingPathExtension;
NSString *ext = name.pathExtension;
NSString *path = nil;
CGFloat scale = 1;
// If no extension, guess by system supported (same as UIImage).
NSArray *exts = ext.length > 0 ? @[ext] : @[@"", @"png", @"jpeg", @"jpg", @"gif", @"webp", @"apng"];
NSArray *scales = _NSBundlePreferredScales();
for (int s = 0; s < scales.count; s++) {
scale = ((NSNumber *)scales[s]).floatValue;
NSString *scaledName = _NSStringByAppendingNameScale(res, scale);
for (NSString *e in exts) {
path = [[NSBundle mainBundle] pathForResource:scaledName ofType:e];
if (path) break;
}
if (path) break;
}
if (path.length == 0) return nil;
NSData *data = [NSData dataWithContentsOfFile:path];
if (data.length == 0) return nil;
return [[self alloc] initWithData:data scale:scale];
}
+ (YYImage *)imageWithContentsOfFile:(NSString *)path {
return [[self alloc] initWithContentsOfFile:path];
}
+ (YYImage *)imageWithData:(NSData *)data {
return [[self alloc] initWithData:data];
}
+ (YYImage *)imageWithData:(NSData *)data scale:(CGFloat)scale {
return [[self alloc] initWithData:data scale:scale];
}
- (instancetype)initWithContentsOfFile:(NSString *)path {
NSData *data = [NSData dataWithContentsOfFile:path];
return [self initWithData:data scale:_NSStringPathScale(path)];
}
- (instancetype)initWithData:(NSData *)data {
return [self initWithData:data scale:1];
}
- (instancetype)initWithData:(NSData *)data scale:(CGFloat)scale {
if (data.length == 0) return nil;
if (scale <= 0) scale = [UIScreen mainScreen].scale;
_preloadedLock = dispatch_semaphore_create(1);
@autoreleasepool {
YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:scale];
YYImageFrame *frame = [decoder frameAtIndex:0 decodeForDisplay:YES];
UIImage *image = frame.image;
if (!image) return nil;
self = [self initWithCGImage:image.CGImage scale:decoder.scale orientation:image.imageOrientation];
if (!self) return nil;
_animatedImageType = decoder.type;
if (decoder.frameCount > 1) {
_decoder = decoder;
_bytesPerFrame = CGImageGetBytesPerRow(image.CGImage) * CGImageGetHeight(image.CGImage);
_animatedImageMemorySize = _bytesPerFrame * decoder.frameCount;
}
self.yy_isDecodedForDisplay = YES;
}
return self;
}
- (NSData *)animatedImageData {
return _decoder.data;
}
- (void)setPreloadAllAnimatedImageFrames:(BOOL)preloadAllAnimatedImageFrames {
if (_preloadAllAnimatedImageFrames != preloadAllAnimatedImageFrames) {
if (preloadAllAnimatedImageFrames && _decoder.frameCount > 0) {
NSMutableArray *frames = [NSMutableArray new];
for (NSUInteger i = 0, max = _decoder.frameCount; i < max; i++) {
UIImage *img = [self animatedImageFrameAtIndex:i];
if (img) {
[frames addObject:img];
} else {
[frames addObject:[NSNull null]];
}
}
dispatch_semaphore_wait(_preloadedLock, DISPATCH_TIME_FOREVER);
_preloadedFrames = frames;
dispatch_semaphore_signal(_preloadedLock);
} else {
dispatch_semaphore_wait(_preloadedLock, DISPATCH_TIME_FOREVER);
_preloadedFrames = nil;
dispatch_semaphore_signal(_preloadedLock);
}
}
}
#pragma mark - protocol NSCoding
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
NSNumber *scale = [aDecoder decodeObjectForKey:@"YYImageScale"];
NSData *data = [aDecoder decodeObjectForKey:@"YYImageData"];
if (data.length) {
self = [self initWithData:data scale:scale.doubleValue];
} else {
self = [super initWithCoder:aDecoder];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
if (_decoder.data.length) {
[aCoder encodeObject:@(self.scale) forKey:@"YYImageScale"];
[aCoder encodeObject:_decoder.data forKey:@"YYImageData"];
} else {
[super encodeWithCoder:aCoder]; // Apple use UIImagePNGRepresentation() to encode UIImage.
}
}
#pragma mark - protocol YYAnimatedImage
- (NSUInteger)animatedImageFrameCount {
return _decoder.frameCount;
}
- (NSUInteger)animatedImageLoopCount {
return _decoder.loopCount;
}
- (NSUInteger)animatedImageBytesPerFrame {
return _bytesPerFrame;
}
- (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {
if (index >= _decoder.frameCount) return nil;
dispatch_semaphore_wait(_preloadedLock, DISPATCH_TIME_FOREVER);
UIImage *image = _preloadedFrames[index];
dispatch_semaphore_signal(_preloadedLock);
if (image) return image == (id)[NSNull null] ? nil : image;
return [_decoder frameAtIndex:index decodeForDisplay:YES].image;
}
- (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index {
NSTimeInterval duration = [_decoder frameDurationAtIndex:index];
/*
http://opensource.apple.com/source/WebCore/WebCore-7600.1.25/platform/graphics/cg/ImageSourceCG.cpp
Many annoying ads specify a 0 duration to make an image flash as quickly as
possible. We follow Safari and Firefox's behavior and use a duration of 100 ms
for any frames that specify a duration of <= 10 ms.
See <rdar://problem/7689300> and <http://webkit.org/b/36082> for more information.
See also: http://nullsleep.tumblr.com/post/16524517190/animated-gif-minimum-frame-delay-browser.
*/
if (duration < 0.011f) return 0.100f;
return duration;
}
@end

505
Pods/YYImage/YYImage/YYImageCoder.h generated Normal file
View File

@@ -0,0 +1,505 @@
//
// YYImageCoder.h
// YYImage <https://github.com/ibireme/YYImage>
//
// Created by ibireme on 15/5/13.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
Image file type.
*/
typedef NS_ENUM(NSUInteger, YYImageType) {
YYImageTypeUnknown = 0, ///< unknown
YYImageTypeJPEG, ///< jpeg, jpg
YYImageTypeJPEG2000, ///< jp2
YYImageTypeTIFF, ///< tiff, tif
YYImageTypeBMP, ///< bmp
YYImageTypeICO, ///< ico
YYImageTypeICNS, ///< icns
YYImageTypeGIF, ///< gif
YYImageTypePNG, ///< png
YYImageTypeWebP, ///< webp
YYImageTypeOther, ///< other image format
};
/**
Dispose method specifies how the area used by the current frame is to be treated
before rendering the next frame on the canvas.
*/
typedef NS_ENUM(NSUInteger, YYImageDisposeMethod) {
/**
No disposal is done on this frame before rendering the next; the contents
of the canvas are left as is.
*/
YYImageDisposeNone = 0,
/**
The frame's region of the canvas is to be cleared to fully transparent black
before rendering the next frame.
*/
YYImageDisposeBackground,
/**
The frame's region of the canvas is to be reverted to the previous contents
before rendering the next frame.
*/
YYImageDisposePrevious,
};
/**
Blend operation specifies how transparent pixels of the current frame are
blended with those of the previous canvas.
*/
typedef NS_ENUM(NSUInteger, YYImageBlendOperation) {
/**
All color components of the frame, including alpha, overwrite the current
contents of the frame's canvas region.
*/
YYImageBlendNone = 0,
/**
The frame should be composited onto the output buffer based on its alpha.
*/
YYImageBlendOver,
};
/**
An image frame object.
*/
@interface YYImageFrame : NSObject <NSCopying>
@property (nonatomic) NSUInteger index; ///< Frame index (zero based)
@property (nonatomic) NSUInteger width; ///< Frame width
@property (nonatomic) NSUInteger height; ///< Frame height
@property (nonatomic) NSUInteger offsetX; ///< Frame origin.x in canvas (left-bottom based)
@property (nonatomic) NSUInteger offsetY; ///< Frame origin.y in canvas (left-bottom based)
@property (nonatomic) NSTimeInterval duration; ///< Frame duration in seconds
@property (nonatomic) YYImageDisposeMethod dispose; ///< Frame dispose method.
@property (nonatomic) YYImageBlendOperation blend; ///< Frame blend operation.
@property (nullable, nonatomic, strong) UIImage *image; ///< The image.
+ (instancetype)frameWithImage:(UIImage *)image;
@end
#pragma mark - Decoder
/**
An image decoder to decode image data.
@discussion This class supports decoding animated WebP, APNG, GIF and system
image format such as PNG, JPG, JP2, BMP, TIFF, PIC, ICNS and ICO. It can be used
to decode complete image data, or to decode incremental image data during image
download. This class is thread-safe.
Example:
// Decode single image:
NSData *data = [NSData dataWithContentOfFile:@"/tmp/image.webp"];
YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:2.0];
UIImage image = [decoder frameAtIndex:0 decodeForDisplay:YES].image;
// Decode image during download:
NSMutableData *data = [NSMutableData new];
YYImageDecoder *decoder = [[YYImageDecoder alloc] initWithScale:2.0];
while(newDataArrived) {
[data appendData:newData];
[decoder updateData:data final:NO];
if (decoder.frameCount > 0) {
UIImage image = [decoder frameAtIndex:0 decodeForDisplay:YES].image;
// progressive display...
}
}
[decoder updateData:data final:YES];
UIImage image = [decoder frameAtIndex:0 decodeForDisplay:YES].image;
// final display...
*/
@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 scale.
@property (nonatomic, readonly) NSUInteger frameCount; ///< Image frame count.
@property (nonatomic, readonly) NSUInteger loopCount; ///< Image loop count, 0 means infinite.
@property (nonatomic, readonly) NSUInteger width; ///< Image canvas width.
@property (nonatomic, readonly) NSUInteger height; ///< Image canvas height.
@property (nonatomic, readonly, getter=isFinalized) BOOL finalized;
/**
Creates an image decoder.
@param scale Image's scale.
@return An image decoder.
*/
- (instancetype)initWithScale:(CGFloat)scale NS_DESIGNATED_INITIALIZER;
/**
Updates the incremental image with new data.
@discussion You can use this method to decode progressive/interlaced/baseline
image when you do not have the complete image data. The `data` was retained by
decoder, you should not modify the data in other thread during decoding.
@param data The data to add to the image decoder. Each time you call this
function, the 'data' parameter must contain all of the image file data
accumulated so far.
@param final A value that specifies whether the data is the final set.
Pass YES if it is, NO otherwise. When the data is already finalized, you can
not update the data anymore.
@return Whether succeed.
*/
- (BOOL)updateData:(nullable NSData *)data final:(BOOL)final;
/**
Convenience method to create a decoder with specified data.
@param data Image data.
@param scale Image's scale.
@return A new decoder, or nil if an error occurs.
*/
+ (nullable instancetype)decoderWithData:(NSData *)data scale:(CGFloat)scale;
/**
Decodes and returns a frame from a specified index.
@param index Frame image index (zero-based).
@param decodeForDisplay Whether decode the image to memory bitmap for display.
If NO, it will try to returns the original frame data without blend.
@return A new frame with image, or nil if an error occurs.
*/
- (nullable YYImageFrame *)frameAtIndex:(NSUInteger)index decodeForDisplay:(BOOL)decodeForDisplay;
/**
Returns the frame duration from a specified index.
@param index Frame image (zero-based).
@return Duration in seconds.
*/
- (NSTimeInterval)frameDurationAtIndex:(NSUInteger)index;
/**
Returns the frame's properties. See "CGImageProperties.h" in ImageIO.framework
for more information.
@param index Frame image index (zero-based).
@return The ImageIO frame property.
*/
- (nullable NSDictionary *)framePropertiesAtIndex:(NSUInteger)index;
/**
Returns the image's properties. See "CGImageProperties.h" in ImageIO.framework
for more information.
*/
- (nullable NSDictionary *)imageProperties;
@end
#pragma mark - Encoder
/**
An image encoder to encode image to data.
@discussion It supports encoding single frame image with the type defined in YYImageType.
It also supports encoding multi-frame image with GIF, APNG and WebP.
Example:
YYImageEncoder *jpegEncoder = [[YYImageEncoder alloc] initWithType:YYImageTypeJPEG];
jpegEncoder.quality = 0.9;
[jpegEncoder addImage:image duration:0];
NSData jpegData = [jpegEncoder encode];
YYImageEncoder *gifEncoder = [[YYImageEncoder alloc] initWithType:YYImageTypeGIF];
gifEncoder.loopCount = 5;
[gifEncoder addImage:image0 duration:0.1];
[gifEncoder addImage:image1 duration:0.15];
[gifEncoder addImage:image2 duration:0.2];
NSData gifData = [gifEncoder encode];
@warning It just pack the images together when encoding multi-frame image. If you
want to reduce the image file size, try imagemagick/ffmpeg for GIF and WebP,
and apngasm for APNG.
*/
@interface YYImageEncoder : NSObject
@property (nonatomic, readonly) YYImageType type; ///< Image type.
@property (nonatomic) NSUInteger loopCount; ///< Loop count, 0 means infinit, only available for GIF/APNG/WebP.
@property (nonatomic) BOOL lossless; ///< Lossless, only available for WebP.
@property (nonatomic) CGFloat quality; ///< Compress quality, 0.0~1.0, only available for JPG/JP2/WebP.
- (instancetype)init UNAVAILABLE_ATTRIBUTE;
+ (instancetype)new UNAVAILABLE_ATTRIBUTE;
/**
Create an image encoder with a specified type.
@param type Image type.
@return A new encoder, or nil if an error occurs.
*/
- (nullable instancetype)initWithType:(YYImageType)type NS_DESIGNATED_INITIALIZER;
/**
Add an image to encoder.
@param image Image.
@param duration Image duration for animation. Pass 0 to ignore this parameter.
*/
- (void)addImage:(UIImage *)image duration:(NSTimeInterval)duration;
/**
Add an image with image data to encoder.
@param data Image data.
@param duration Image duration for animation. Pass 0 to ignore this parameter.
*/
- (void)addImageWithData:(NSData *)data duration:(NSTimeInterval)duration;
/**
Add an image from a file path to encoder.
@param image Image file path.
@param duration Image duration for animation. Pass 0 to ignore this parameter.
*/
- (void)addImageWithFile:(NSString *)path duration:(NSTimeInterval)duration;
/**
Encodes the image and returns the image data.
@return The image data, or nil if an error occurs.
*/
- (nullable NSData *)encode;
/**
Encodes the image to a file.
@param path The file path (overwrite if exist).
@return Whether succeed.
*/
- (BOOL)encodeToFile:(NSString *)path;
/**
Convenience method to encode single frame image.
@param image The image.
@param type The destination image type.
@param quality Image quality, 0.0~1.0.
@return The image data, or nil if an error occurs.
*/
+ (nullable NSData *)encodeImage:(UIImage *)image type:(YYImageType)type quality:(CGFloat)quality;
/**
Convenience method to encode image from a decoder.
@param decoder The image decoder.
@param type The destination image type;
@param quality Image quality, 0.0~1.0.
@return The image data, or nil if an error occurs.
*/
+ (nullable NSData *)encodeImageWithDecoder:(YYImageDecoder *)decoder type:(YYImageType)type quality:(CGFloat)quality;
@end
#pragma mark - UIImage
@interface UIImage (YYImageCoder)
/**
Decompress this image to bitmap, so when the image is displayed on screen,
the main thread won't be blocked by additional decode. If the image has already
been decoded or unable to decode, it just returns itself.
@return an image decoded, or just return itself if no needed.
@see yy_isDecodedForDisplay
*/
- (instancetype)yy_imageByDecoded;
/**
Wherher the image can be display on screen without additional decoding.
@warning It just a hint for your code, change it has no other effect.
*/
@property (nonatomic) BOOL yy_isDecodedForDisplay;
/**
Saves this image to iOS Photos Album.
@discussion This method attempts to save the original data to album if the
image is created from an animated GIF/APNG, otherwise, it will save the image
as JPEG or PNG (based on the alpha information).
@param completionBlock The block invoked (in main thread) after the save operation completes.
assetURL: An URL that identifies the saved image file. If the image is not saved, assetURL is nil.
error: If the image is not saved, an error object that describes the reason for failure, otherwise nil.
*/
- (void)yy_saveToAlbumWithCompletionBlock:(nullable void(^)(NSURL * _Nullable assetURL, NSError * _Nullable error))completionBlock;
/**
Return a 'best' data representation for this image.
@discussion The convertion based on these rule:
1. If the image is created from an animated GIF/APNG/WebP, it returns the original data.
2. It returns PNG or JPEG(0.9) representation based on the alpha information.
@return Image data, or nil if an error occurs.
*/
- (nullable NSData *)yy_imageDataRepresentation;
@end
#pragma mark - Helper
/// Detect a data's image type by reading the data's header 16 bytes (very fast).
CG_EXTERN YYImageType YYImageDetectType(CFDataRef data);
/// Convert YYImageType to UTI (such as kUTTypeJPEG).
CG_EXTERN CFStringRef _Nullable YYImageTypeToUTType(YYImageType type);
/// Convert UTI (such as kUTTypeJPEG) to YYImageType.
CG_EXTERN YYImageType YYImageTypeFromUTType(CFStringRef uti);
/// Get image type's file extension (such as @"jpg").
CG_EXTERN NSString *_Nullable YYImageTypeGetExtension(YYImageType type);
/// Returns the shared DeviceRGB color space.
CG_EXTERN CGColorSpaceRef YYCGColorSpaceGetDeviceRGB();
/// Returns the shared DeviceGray color space.
CG_EXTERN CGColorSpaceRef YYCGColorSpaceGetDeviceGray();
/// Returns whether a color space is DeviceRGB.
CG_EXTERN BOOL YYCGColorSpaceIsDeviceRGB(CGColorSpaceRef space);
/// Returns whether a color space is DeviceGray.
CG_EXTERN BOOL YYCGColorSpaceIsDeviceGray(CGColorSpaceRef space);
/// Convert EXIF orientation value to UIImageOrientation.
CG_EXTERN UIImageOrientation YYUIImageOrientationFromEXIFValue(NSInteger value);
/// Convert UIImageOrientation to EXIF orientation value.
CG_EXTERN NSInteger YYUIImageOrientationToEXIFValue(UIImageOrientation orientation);
/**
Create a decoded image.
@discussion If the source image is created from a compressed image data (such as
PNG or JPEG), you can use this method to decode the image. After decoded, you can
access the decoded bytes with CGImageGetDataProvider() and CGDataProviderCopyData()
without additional decode process. If the image has already decoded, this method
just copy the decoded bytes to the new image.
@param imageRef The source image.
@param decodeForDisplay If YES, this method will decode the image and convert
it to BGRA8888 (premultiplied) or BGRX8888 format for CALayer display.
@return A decoded image, or NULL if an error occurs.
*/
CG_EXTERN CGImageRef _Nullable YYCGImageCreateDecodedCopy(CGImageRef imageRef, BOOL decodeForDisplay);
/**
Create an image copy with an orientation.
@param imageRef Source image
@param orientation Image orientation which will applied to the image.
@param destBitmapInfo Destimation image bitmap, only support 32bit format (such as ARGB8888).
@return A new image, or NULL if an error occurs.
*/
CG_EXTERN CGImageRef _Nullable YYCGImageCreateCopyWithOrientation(CGImageRef imageRef,
UIImageOrientation orientation,
CGBitmapInfo destBitmapInfo);
/**
Create an image copy with CGAffineTransform.
@param imageRef Source image.
@param transform Transform applied to image (left-bottom based coordinate system).
@param destSize Destination image size
@param destBitmapInfo Destimation image bitmap, only support 32bit format (such as ARGB8888).
@return A new image, or NULL if an error occurs.
*/
CG_EXTERN CGImageRef _Nullable YYCGImageCreateAffineTransformCopy(CGImageRef imageRef,
CGAffineTransform transform,
CGSize destSize,
CGBitmapInfo destBitmapInfo);
/**
Encode an image to data with CGImageDestination.
@param imageRef The image.
@param type The image destination data type.
@param quality The quality (0.0~1.0)
@return A new image data, or nil if an error occurs.
*/
CG_EXTERN CFDataRef _Nullable YYCGImageCreateEncodedData(CGImageRef imageRef, YYImageType type, CGFloat quality);
/**
Whether WebP is available in YYImage.
*/
CG_EXTERN BOOL YYImageWebPAvailable();
/**
Get a webp image frame count;
@param webpData WebP data.
@return Image frame count, or 0 if an error occurs.
*/
CG_EXTERN NSUInteger YYImageGetWebPFrameCount(CFDataRef webpData);
/**
Decode an image from WebP data, returns NULL if an error occurs.
@param webpData The WebP data.
@param decodeForDisplay If YES, this method will decode the image and convert it
to BGRA8888 (premultiplied) format for CALayer display.
@param useThreads YES to enable multi-thread decode.
(speed up, but cost more CPU)
@param bypassFiltering YES to skip the in-loop filtering.
(speed up, but may lose some smooth)
@param noFancyUpsampling YES to use faster pointwise upsampler.
(speed down, and may lose some details).
@return The decoded image, or NULL if an error occurs.
*/
CG_EXTERN CGImageRef _Nullable YYCGImageCreateWithWebPData(CFDataRef webpData,
BOOL decodeForDisplay,
BOOL useThreads,
BOOL bypassFiltering,
BOOL noFancyUpsampling);
typedef NS_ENUM(NSUInteger, YYImagePreset) {
YYImagePresetDefault = 0, ///< default preset.
YYImagePresetPicture, ///< digital picture, like portrait, inner shot
YYImagePresetPhoto, ///< outdoor photograph, with natural lighting
YYImagePresetDrawing, ///< hand or line drawing, with high-contrast details
YYImagePresetIcon, ///< small-sized colorful images
YYImagePresetText ///< text-like
};
/**
Encode a CGImage to WebP data
@param imageRef image
@param lossless YES=lossless (similar to PNG), NO=lossy (similar to JPEG)
@param quality 0.0~1.0 (0=smallest file, 1.0=biggest file)
For lossless image, try the value near 1.0; for lossy, try the value near 0.8.
@param compressLevel 0~6 (0=fast, 6=slower-better). Default is 4.
@param preset Preset for different image type, default is YYImagePresetDefault.
@return WebP data, or nil if an error occurs.
*/
CG_EXTERN CFDataRef _Nullable YYCGImageCreateEncodedWebPData(CGImageRef imageRef,
BOOL lossless,
CGFloat quality,
int compressLevel,
YYImagePreset preset);
NS_ASSUME_NONNULL_END

2870
Pods/YYImage/YYImage/YYImageCoder.m generated Normal file

File diff suppressed because it is too large Load Diff

104
Pods/YYImage/YYImage/YYSpriteSheetImage.h generated Normal file
View File

@@ -0,0 +1,104 @@
//
// YYSpriteImage.h
// YYImage <https://github.com/ibireme/YYImage>
//
// Created by ibireme on 15/4/21.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
#if __has_include(<YYImage/YYImage.h>)
#import <YYImage/YYAnimatedImageView.h>
#elif __has_include(<YYWebImage/YYImage.h>)
#import <YYWebImage/YYAnimatedImageView.h>
#else
#import "YYAnimatedImageView.h"
#endif
NS_ASSUME_NONNULL_BEGIN
/**
An image to display sprite sheet animation.
@discussion It is a fully compatible `UIImage` subclass.
The animation can be played by YYAnimatedImageView.
Sample Code:
// 8 * 12 sprites in a single sheet image
UIImage *spriteSheet = [UIImage imageNamed:@"sprite-sheet"];
NSMutableArray *contentRects = [NSMutableArray new];
NSMutableArray *durations = [NSMutableArray new];
for (int j = 0; j < 12; j++) {
for (int i = 0; i < 8; i++) {
CGRect rect;
rect.size = CGSizeMake(img.size.width / 8, img.size.height / 12);
rect.origin.x = img.size.width / 8 * i;
rect.origin.y = img.size.height / 12 * j;
[contentRects addObject:[NSValue valueWithCGRect:rect]];
[durations addObject:@(1 / 60.0)];
}
}
YYSpriteSheetImage *sprite;
sprite = [[YYSpriteSheetImage alloc] initWithSpriteSheetImage:img
contentRects:contentRects
frameDurations:durations
loopCount:0];
YYAnimatedImageView *imgView = [YYAnimatedImageView new];
imgView.size = CGSizeMake(img.size.width / 8, img.size.height / 12);
imgView.image = sprite;
@discussion It can also be used to display single frame in sprite sheet image.
Sample Code:
YYSpriteSheetImage *sheet = ...;
UIImageView *imageView = ...;
imageView.image = sheet;
imageView.layer.contentsRect = [sheet contentsRectForCALayerAtIndex:6];
*/
@interface YYSpriteSheetImage : UIImage <YYAnimatedImage>
/**
Creates and returns an image object.
@param image The sprite sheet image (contains all frames).
@param contentRects The sprite sheet image frame rects in the image coordinates.
The rectangle should not outside the image's bounds. The objects in this array
should be created with [NSValue valueWithCGRect:].
@param frameDurations The sprite sheet image frame's durations in seconds.
The objects in this array should be NSNumber.
@param loopCount Animation loop count, 0 means infinite looping.
@return An image object, or nil if an error occurs.
*/
- (nullable instancetype)initWithSpriteSheetImage:(UIImage *)image
contentRects:(NSArray<NSValue *> *)contentRects
frameDurations:(NSArray<NSNumber *> *)frameDurations
loopCount:(NSUInteger)loopCount;
@property (nonatomic, readonly) NSArray<NSValue *> *contentRects;
@property (nonatomic, readonly) NSArray<NSValue *> *frameDurations;
@property (nonatomic, readonly) NSUInteger loopCount;
/**
Get the contents rect for CALayer.
See "contentsRect" property in CALayer for more information.
@param index Index of frame.
@return Contents Rect.
*/
- (CGRect)contentsRectForCALayerAtIndex:(NSUInteger)index;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,80 @@
//
// YYSpriteImage.m
// YYImage <https://github.com/ibireme/YYImage>
//
// Created by ibireme on 15/4/21.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "YYSpriteSheetImage.h"
@implementation YYSpriteSheetImage
- (instancetype)initWithSpriteSheetImage:(UIImage *)image
contentRects:(NSArray *)contentRects
frameDurations:(NSArray *)frameDurations
loopCount:(NSUInteger)loopCount {
if (!image.CGImage) return nil;
if (contentRects.count < 1 || frameDurations.count < 1) return nil;
if (contentRects.count != frameDurations.count) return nil;
self = [super initWithCGImage:image.CGImage scale:image.scale orientation:image.imageOrientation];
if (!self) return nil;
_contentRects = contentRects.copy;
_frameDurations = frameDurations.copy;
_loopCount = loopCount;
return self;
}
- (CGRect)contentsRectForCALayerAtIndex:(NSUInteger)index {
CGRect layerRect = CGRectMake(0, 0, 1, 1);
if (index >= _contentRects.count) return layerRect;
CGSize imageSize = self.size;
CGRect rect = [self animatedImageContentsRectAtIndex:index];
if (imageSize.width > 0.01 && imageSize.height > 0.01) {
layerRect.origin.x = rect.origin.x / imageSize.width;
layerRect.origin.y = rect.origin.y / imageSize.height;
layerRect.size.width = rect.size.width / imageSize.width;
layerRect.size.height = rect.size.height / imageSize.height;
layerRect = CGRectIntersection(layerRect, CGRectMake(0, 0, 1, 1));
if (CGRectIsNull(layerRect) || CGRectIsEmpty(layerRect)) {
layerRect = CGRectMake(0, 0, 1, 1);
}
}
return layerRect;
}
#pragma mark @protocol YYAnimatedImage
- (NSUInteger)animatedImageFrameCount {
return _contentRects.count;
}
- (NSUInteger)animatedImageLoopCount {
return _loopCount;
}
- (NSUInteger)animatedImageBytesPerFrame {
return 0;
}
- (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {
return self;
}
- (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index {
if (index >= _frameDurations.count) return 0;
return ((NSNumber *)_frameDurations[index]).doubleValue;
}
- (CGRect)animatedImageContentsRectAtIndex:(NSUInteger)index {
if (index >= _contentRects.count) return CGRectZero;
return ((NSValue *)_contentRects[index]).CGRectValue;
}
@end