TwitterImagePipeline/TIPImageCodecs.h (117 lines of code) (raw):

// // TIPImageCodecs.h // TwitterImagePipeline // // Created on 11/7/16. // Copyright © 2020 Twitter. All rights reserved. // #import <CoreGraphics/CGGeometry.h> #import <Foundation/Foundation.h> #import <TwitterImagePipeline/TIPImageTypes.h> #import <UIKit/UIView.h> // UIViewContentMode @protocol TIPImageEncoder; @protocol TIPImageDecoder; @protocol TIPImageDecoderContext; @protocol TIPImageCodec; @class UIImage; @class TIPImageContainer; NS_ASSUME_NONNULL_BEGIN #pragma mark - Constants /** Properties of a codec */ typedef NS_OPTIONS(NSInteger, TIPImageCodecProperties) { /** no properties */ TIPImageCodecNoProperties = 0, /** can decode */ TIPImageCodecSupportsDecoding = 1 << 0, /** can encode */ TIPImageCodecSupportsEncoding = 1 << 1, /** supports animations */ TIPImageCodecSupportsAnimation = 1 << 2, /** supports decoding progressively */ TIPImageCodecSupportsProgressiveLoading = 1 << 3, }; /** Result when a `TIPImageDecoder` is detecting if the given data matches what the decoder can decode */ typedef NS_ENUM(NSInteger, TIPImageDecoderDetectionResult) { /** no match, this decoder will not support decoding the given image data */ TIPImageDecoderDetectionResultNoMatch = -1, /** need more data, this decoder needs more data before it can determine if it is a match */ TIPImageDecoderDetectionResultNeedMoreData = 0, /** match, this decoder can decode the provided image data */ TIPImageDecoderDetectionResultMatch = 1, }; /** Result after appending data to a decoder or finalizing the decoder */ typedef NS_ENUM(NSInteger, TIPImageDecoderAppendResult) { /** progress was made, but nothing significant was accomplished */ TIPImageDecoderAppendResultDidProgress = 0, /** headers were loaded */ TIPImageDecoderAppendResultDidLoadHeaders = 1, /** at least 1 frame finished loading */ TIPImageDecoderAppendResultDidLoadFrame = 2, /** the image finished loading */ TIPImageDecoderAppendResultDidCompleteLoading = 3, }; /** The mode to render with the `TIPImageDecoder`. Decoders should consider rendering at the greediest capability that comes close to a given render mode without exceeding. Modes are ordered from most greedy to least greedy. */ typedef NS_ENUM(NSInteger, TIPImageDecoderRenderMode) { /** greedily render whatever the decoder can support */ TIPImageDecoderRenderModeAnyProgress = 0, /** render the latest full frame of progress */ TIPImageDecoderRenderModeFullFrameProgress = 1, /** render the completed image */ TIPImageDecoderRenderModeCompleteImage = 2, }; #pragma mark - Protocols /** Protocol for an image codec */ @protocol TIPImageCodec <NSObject> @required /** Return the `TIPImageDecoder` for this codec */ - (id<TIPImageDecoder>)tip_decoder; /** Return the `TIPImageEncoder` for this codec. Return `nil` to indicate this codec does not support writing/encoding. */ - (nullable id<TIPImageEncoder>)tip_encoder; @optional /** Return `YES` to indicate the codec is for animated images */ - (BOOL)tip_isAnimated; @end /** Protocol for an image encoder */ @protocol TIPImageEncoder <NSObject> @required /** Write the target `TIPImageContainer` to an `NSData` instance @param encodingOptions the `TIPImageEncodingOptions` to encode with @param quality the quality to encode with. Value is between `0` and `1`. `1.f` for lossless. @param error the error if one was encountered @return the encoded image as an `NSData` or `nil` if there was an error */ - (nullable NSData *)tip_writeDataWithImage:(TIPImageContainer *)image encodingOptions:(TIPImageEncodingOptions)encodingOptions suggestedQuality:(float)quality error:(out NSError * __nullable * __nullable)error; @optional /** Write the target `UIImage` to file path (optional) If not provided, writing this encoder's codec to disk will use the `"write data"` method @param filePath the path to write the image to @param image the `TIPImageContainer` to encode @param encodingOptions the `TIPImageEncodingOptions` to encode with @param quality the quality to encode with. Value is between `0` and `1`. `1.f` for lossless. @param error the error if one was encountered @param atomic if the writing of the file should be atomic @return `YES` on success, `NO` on failure */ - (BOOL)tip_writeToFile:(NSString *)filePath withImage:(TIPImageContainer *)image encodingOptions:(TIPImageEncodingOptions)encodingOptions suggestedQuality:(float)quality atomically:(BOOL)atomic error:(out NSError * __nullable * __nullable)error; @end /** Protocol for decoder context */ @protocol TIPImageDecoderContext <NSObject> @required /** the optionally provided config object */ @property (nonatomic, readonly, nullable) id tip_config; /** expose the current buffer of data */ @property (nonatomic, readonly) NSData *tip_data; /** expose the dimensions of the image being decoded */ @property (nonatomic, readonly) CGSize tip_dimensions; /** Expose the frame count of the image being decoded. Animations and progressive images will have multiple frames. Static images will only have 1 frame (once the complete image is loaded). */ @property (nonatomic, readonly) NSUInteger tip_frameCount; @optional /** decoding is being done progressively */ @property (nonatomic, readonly) BOOL tip_isProgressive; /** decoding an animation */ @property (nonatomic, readonly) BOOL tip_isAnimated; /** decoding an image with alpha */ @property (nonatomic, readonly) BOOL tip_hasAlpha; /** decoding detected GPS info, which is a no-no */ @property (nonatomic, readonly) BOOL tip_hasGPSInfo; @end /** Protocol for an image decoder // Pattern for decoding (in pseudo code): if (Match == decoder.DetectIfDecodable()) { context = decoder.InitiateContext() while (data = MoreData()) { decoder.Append(context, data) ... optional ... { image = decoder.Render(context) } } decoder.Finalize(context) image = decoder.Render(context) } */ @protocol TIPImageDecoder <NSObject> @required /** Detect if the given _data_ can be decoded. @param data the image data to decode (can be incomplete) @param complete `YES` if the image data to decode is complete, otherwise pass `NO` @param imageType a guess as to the image type, can be `nil` @return `Match` if decodable, `NoMatch` if not decodable and `NeedMoreData` if inconclusive */ - (TIPImageDecoderDetectionResult)tip_detectDecodableData:(NSData *)data isCompleteData:(BOOL)complete earlyGuessImageType:(nullable NSString *)imageType; /** Initiate decoding, will be called first in the decoding process. Will always be balanced with a `tip_finalizeDecoding:` if the image data loading completes. @param config an optional opaque object to provide extra customization for how the decoding should operate, totally safe for implementation to ignore @param expectedDataLength the expected length of the full image data to decode @param buffer a prefilled buffer to start decoding with, can be `nil`. Can safely use this as the image decoding buffer or just copy the data from the buffer. @return a context to use throughout the decoding process (to help maintain state) */ - (id<TIPImageDecoderContext>)tip_initiateDecoding:(nullable id)config expectedDataLength:(NSUInteger)expectedDataLength buffer:(nullable NSMutableData *)buffer; /** Append data to a decoding Decoder's can just return `Progress` if they are just buffering the data and not decoding on the fly, which is fine. @param context the context to use when appending data and updating state @param data the non-nil data to append (though it can be zero-length) @return the result of the append if decoding on the fly. */ - (TIPImageDecoderAppendResult)tip_append:(id<TIPImageDecoderContext>)context data:(NSData *)data; /** Render the image with given _mode_ with whatever progress has been made. Simplest implementation is to return `nil` until the decoding has been finalized (marked as _complete_) and then return the fully decoded image. More advanced decoders will progressively offer results and intelligently cache the progressive renders in the _context_ to avoid redundant decoding work when no tangible progress has happened. Can be called anytime after the decoding has initiated including after being finalized. The target sizing arguments (_targetDimensions_ and _targetContentMode_) are optional for the decoder. Advanced decoders will decode directly to the target sizing given, reducing RAM overhead of decoding the full size first. If a codec does not support target size based decoding, they _SHOULD NOT_ scale the decoded full size image and instead just return the full size image for __TIP__ to handle scaling. @param context the context to use when rendering the image @param renderMode the `TIPImageDecoderRenderMode` mode to render with @param targetDimensions the dimension sizing constraints to decode the image into (`CGSizeZero` for full size) -- can be ignored by codec to simplify implementation @param targetContentMode the content mode sizing constraints to decode the image into (any non-scaling mode for full size) -- can be ignored by codec to simplify implementation @return an image (encapsulated in a `TIPImageContainer`) or `nil`. */ - (nullable TIPImageContainer *)tip_renderImage:(id<TIPImageDecoderContext>)context renderMode:(TIPImageDecoderRenderMode)renderMode targetDimensions:(CGSize)targetDimensions targetContentMode:(UIViewContentMode)targetContentMode; /** Finalize the decoding, only called after all image data has been provided with `tip_append:data:`. @param context the context whose decoding is being finalized @return the result of the finalize. */ - (TIPImageDecoderAppendResult)tip_finalizeDecoding:(id<TIPImageDecoderContext>)context; @optional /** This decoder supports decoding progressively (if provided a progressive image to decode) */ - (BOOL)tip_supportsProgressiveDecoding; /** Optional implementation for _quick_ decoding. Some decoders have alternative decoding mechanism when the entire image data is available. Implementing this method will offer that feature to __TIP__. Otherwise, the normal decoding pattern will be used (init, append, finalize & render). @param imageData the image data to decode @param targetDimensions the dimension sizing constraints to decode the image into (`CGSizeZero` for full size) -- can be ignored by codec to simplify implementation @param targetContentMode the content mode sizing constraints to decode the image into (any non-scaling mode for full size) -- can be ignored by codec to simplify implementation @param config an optional opaque object to provide extra customization for how the decoding should operate @return the decoded image (wrapped in a `TIPImageContainer`) or `nil` if the image could not be decoded */ - (nullable TIPImageContainer *)tip_decodeImageWithData:(NSData *)imageData targetDimensions:(CGSize)targetDimensions targetContentMode:(UIViewContentMode)targetContentMode config:(nullable id)config; - (nullable TIPImageContainer *)tip_decodeImageWithData:(NSData *)imageData config:(nullable id)config __attribute__((deprecated("Implement tip_decodeImageWithData:targetDimensions:targetContentMode:config:"))); @end #pragma mark - Convenience Functions //! Convenience function to decode an image from data FOUNDATION_EXTERN TIPImageContainer * __nullable TIPDecodeImageFromData(id<TIPImageCodec> codec, id __nullable config, NSData *imageData, CGSize targetDimensions, UIViewContentMode targetContentMode) __attribute__((overloadable)); //! Convenience function to decode an image from data, with a guess at the image type to be decoded FOUNDATION_EXTERN TIPImageContainer * __nullable TIPDecodeImageFromData(id<TIPImageCodec> codec, id __nullable config, NSData *imageData, CGSize targetDimensions, UIViewContentMode targetContentMode, NSString * __nullable earlyGuessImageType) __attribute__((overloadable)); //! Convenience function to encode an image to a file FOUNDATION_EXTERN BOOL TIPEncodeImageToFile(id<TIPImageCodec> codec, TIPImageContainer *imageContainer, NSString *filePath, TIPImageEncodingOptions options, float quality, BOOL atomic, NSError * __nullable * __nullable error); NS_ASSUME_NONNULL_END