//
|
// 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
|