TwitterImagePipeline/TIPImageCodecCatalogue.m (272 lines of code) (raw):
//
// TIPImageCodecCatalogue.m
// TwitterImagePipeline
//
// Created on 11/9/16.
// Copyright © 2020 Twitter. All rights reserved.
//
#import "TIP_Project.h"
#import "TIPDefaultImageCodecs.h"
#import "TIPError.h"
#import "TIPGlobalConfiguration.h"
#import "TIPImageCodecCatalogue.h"
NS_ASSUME_NONNULL_BEGIN
@implementation TIPImageCodecCatalogue
{
dispatch_queue_t _codecQueue;
NSMutableDictionary<NSString *, id<TIPImageCodec>> *_codecs;
}
+ (NSDictionary<NSString *, id<TIPImageCodec>> *)defaultCodecs
{
static NSDictionary<NSString *, id<TIPImageCodec>> *sDefaultsCodecs = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSMutableDictionary<NSString *, id<TIPImageCodec>> *codecs = [NSMutableDictionary dictionary];
NSArray<NSString *> *knownImageTypes = @[
TIPImageTypeJPEG,
TIPImageTypeJPEG2000,
TIPImageTypePNG,
TIPImageTypeGIF,
TIPImageTypeTIFF,
TIPImageTypeBMP,
TIPImageTypeTARGA,
TIPImageTypePICT,
TIPImageTypeQTIF,
TIPImageTypeICNS,
TIPImageTypeICO,
TIPImageTypeRAW,
TIPImageTypeHEIC,
TIPImageTypeAVCI,
TIPImageTypeWEBP,
];
for (NSString *imageType in knownImageTypes) {
id<TIPImageCodec> codec = [TIPBasicCGImageSourceCodec codecWithImageType:imageType];
if (codec) {
codecs[imageType] = codec;
}
}
sDefaultsCodecs = [codecs copy];
});
return sDefaultsCodecs;
}
+ (instancetype)sharedInstance
{
static TIPImageCodecCatalogue *sCatalogue = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
const BOOL loadAsync = [TIPGlobalConfiguration sharedInstance].loadCodecsAsync;
if (loadAsync) {
sCatalogue = [[TIPImageCodecCatalogue alloc] initWithCodecsProvider:^NSDictionary<NSString *,id<TIPImageCodec>> * _Nullable{
return [self defaultCodecs];
}];
} else {
sCatalogue = [[TIPImageCodecCatalogue alloc] initWithCodecs:[self defaultCodecs]];
}
});
return sCatalogue;
}
- (instancetype)init
{
return [self initWithCodecs:nil];
}
- (instancetype)initWithCodecsProvider:(TIPImageCodecCatalogueCodecsProvider)codecProvider
{
if (self = [super init]) {
_codecQueue = dispatch_queue_create("TIPImageCodecCatalogue.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_barrier_async(_codecQueue, ^{
self->_codecs = [NSMutableDictionary dictionaryWithDictionary:codecProvider()];
});
}
return self;
}
- (instancetype)initWithCodecs:(nullable NSDictionary<NSString *,id<TIPImageCodec>> *)codecs
{
if (self = [super init]) {
_codecQueue = dispatch_queue_create("TIPImageCodecCatalogue.queue", DISPATCH_QUEUE_CONCURRENT);
_codecs = [NSMutableDictionary dictionaryWithDictionary:codecs];
}
return self;
}
- (NSDictionary<NSString *, id<TIPImageCodec>> *)allCodecs
{
__block NSDictionary<NSString *, id<TIPImageCodec>> * allCodecs;
dispatch_sync(_codecQueue, ^{
allCodecs = [self->_codecs copy];
});
return allCodecs;
}
- (void)removeCodecForImageType:(NSString *)imageType
{
[self removeCodecForImageType:imageType removedCodec:NULL];
}
- (void)removeCodecForImageType:(NSString *)imageType
removedCodec:(id<TIPImageCodec> __autoreleasing *)codec
{
if (codec) {
dispatch_barrier_sync(_codecQueue, ^{
*codec = self->_codecs[imageType];
[self->_codecs removeObjectForKey:imageType];
});
} else {
tip_dispatch_barrier_async_autoreleasing(_codecQueue, ^{
[self->_codecs removeObjectForKey:imageType];
});
}
}
- (void)setCodec:(id<TIPImageCodec>)codec forImageType:(NSString *)imageType
{
tip_dispatch_barrier_async_autoreleasing(_codecQueue, ^{
self->_codecs[imageType] = codec;
});
}
- (void)replaceCodecForImageType:(NSString *)imageType usingBlock:(id<TIPImageCodec> (^)(id<TIPImageCodec> _Nullable existingCodec))replacementBlock
{
tip_dispatch_barrier_async_autoreleasing(_codecQueue, ^{
id<TIPImageCodec> replacementCodec = replacementBlock(self->_codecs[imageType]);
if (replacementCodec != nil) {
self->_codecs[imageType] = replacementCodec;
}
});
}
- (nullable id<TIPImageCodec>)codecForImageType:(NSString *)imageType
{
__block id<TIPImageCodec> codec;
dispatch_sync(_codecQueue, ^{
codec = self->_codecs[imageType];
});
return codec;
}
@end
@implementation TIPImageCodecCatalogue (KeyedSubscripting)
- (void)setObject:(nullable id<TIPImageCodec>)codec forKeyedSubscript:(NSString *)imageType
{
if (codec) {
[self setCodec:codec forImageType:imageType];
} else {
[self removeCodecForImageType:imageType];
}
}
- (nullable id<TIPImageCodec>)objectForKeyedSubscript:(NSString *)imageType
{
return [self codecForImageType:imageType];
}
@end
@implementation TIPImageCodecCatalogue (Convenience)
- (BOOL)codecWithImageTypeSupportsProgressiveLoading:(nullable NSString *)type
{
return TIP_BITMASK_HAS_SUBSET_FLAGS([self propertiesForCodecWithImageType:type], TIPImageCodecSupportsProgressiveLoading);
}
- (BOOL)codecWithImageTypeSupportsAnimation:(nullable NSString *)type
{
return TIP_BITMASK_HAS_SUBSET_FLAGS([self propertiesForCodecWithImageType:type], TIPImageCodecSupportsAnimation);
}
- (BOOL)codecWithImageTypeSupportsDecoding:(nullable NSString *)type
{
return TIP_BITMASK_HAS_SUBSET_FLAGS([self propertiesForCodecWithImageType:type], TIPImageCodecSupportsDecoding);
}
- (BOOL)codecWithImageTypeSupportsEncoding:(nullable NSString *)type
{
return TIP_BITMASK_HAS_SUBSET_FLAGS([self propertiesForCodecWithImageType:type], TIPImageCodecSupportsEncoding);
}
- (TIPImageCodecProperties)propertiesForCodecWithImageType:(nullable NSString *)type
{
__block id<TIPImageCodec> codec = nil;
if (type) {
dispatch_sync(_codecQueue, ^{
codec = self->_codecs[type];
});
}
TIPImageCodecProperties properties = 0;
if (codec) {
properties |= TIPImageCodecSupportsDecoding;
id<TIPImageDecoder> decoder = [codec tip_decoder];
id<TIPImageEncoder> encoder = [codec tip_encoder];
if (encoder) {
properties |= TIPImageCodecSupportsEncoding;
}
if ([decoder respondsToSelector:@selector(tip_supportsProgressiveDecoding)] && [decoder tip_supportsProgressiveDecoding]) {
properties |= TIPImageCodecSupportsProgressiveLoading;
}
if ([codec respondsToSelector:@selector(tip_isAnimated)] && [codec tip_isAnimated]) {
properties |= TIPImageCodecSupportsAnimation;
}
}
return properties;
}
- (nullable TIPImageContainer *)decodeImageWithData:(NSData *)data
targetDimensions:(CGSize)targetDimensions
targetContentMode:(UIViewContentMode)targetContentMode
decoderConfigMap:(nullable NSDictionary<NSString *, id> *)decoderConfigMap
imageType:(out NSString * __autoreleasing __nullable * __nullable)imageType
{
__block NSString *localImageType = nil;
tip_defer(^{
if (imageType) {
*imageType = localImageType;
}
});
__block TIPImageContainer *container = nil;
NSDictionary<NSString *, id<TIPImageCodec>> *codecs = self.allCodecs;
NSString *guessImageType = TIPDetectImageTypeViaMagicNumbers(data);
if (!guessImageType) {
guessImageType = TIPDetectImageType(data, NULL, NULL, YES);
}
id<TIPImageCodec> guessedCodec = (guessImageType) ? codecs[guessImageType] : nil;
if (guessedCodec) {
id config = decoderConfigMap[guessImageType];
container = TIPDecodeImageFromData(guessedCodec, config, data, targetDimensions, targetContentMode, guessImageType);
if (container) {
localImageType = guessImageType;
return container;
}
}
[codecs enumerateKeysAndObjectsUsingBlock:^(NSString *codecImageType, id<TIPImageCodec> codec, BOOL *stop) {
if (codec != guessedCodec) {
id config = decoderConfigMap[codecImageType];
container = TIPDecodeImageFromData(codec, config, data, targetDimensions, targetContentMode, guessImageType);
if (container) {
*stop = YES;
localImageType = codecImageType;
}
}
}];
return container;
}
- (BOOL)encodeImage:(TIPImageContainer *)image
toFilePath:(NSString *)filePath
withImageType:(NSString *)imageType
quality:(float)quality
options:(TIPImageEncodingOptions)options
atomic:(BOOL)atomic
error:(out NSError **)error
{
id<TIPImageCodec> codec = [self codecForImageType:imageType];
id<TIPImageEncoder> encoder = codec.tip_encoder;
if (!encoder) {
if (error) {
*error = [NSError errorWithDomain:TIPErrorDomain
code:TIPErrorCodeEncodingUnsupported
userInfo:(imageType) ? @{ @"imageType" : imageType } : nil];
}
return NO;
}
return TIPEncodeImageToFile(codec, image, filePath, options, quality, atomic, error);
}
- (nullable NSData *)encodeImage:(TIPImageContainer *)image
withImageType:(NSString *)imageType
quality:(float)quality
options:(TIPImageEncodingOptions)options
error:(out NSError * __autoreleasing __nullable * __nullable)error
{
NSData *data = nil;
id<TIPImageCodec> codec = [self codecForImageType:imageType];
id<TIPImageEncoder> encoder = [codec tip_encoder];
if (!encoder) {
if (error) {
*error = [NSError errorWithDomain:TIPErrorDomain
code:TIPErrorCodeEncodingUnsupported
userInfo:(imageType) ? @{ @"imageType" : imageType } : nil];
}
} else {
data = [encoder tip_writeDataWithImage:image
encodingOptions:options
suggestedQuality:quality
error:error];
}
return data;
}
@end
NS_ASSUME_NONNULL_END