Skip to content

Commit

Permalink
Update to v2.50.0 (#62)
Browse files Browse the repository at this point in the history
- Fix codec detection of small images
- Fix size scaling logic
  • Loading branch information
NSProgrammer committed Oct 29, 2020
1 parent 5ac4bc2 commit c55cb76
Show file tree
Hide file tree
Showing 15 changed files with 226 additions and 58 deletions.
2 changes: 1 addition & 1 deletion BuildConfiguration/TwitterImagePipeline.xcconfig
Expand Up @@ -132,5 +132,5 @@ CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES
CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES

// Versioning
CURRENT_PROJECT_VERSION = 2.24
CURRENT_PROJECT_VERSION = 2.25
VERSIONING_SYSTEM = apple-generic
19 changes: 17 additions & 2 deletions CHANGELOG.md
Expand Up @@ -2,14 +2,29 @@

## Info

**Document version:** 2.24.2
**Document version:** 2.25.0

**Last updated:** 10/22/2020
**Last updated:** 10/29/2020

**Author:** Nolan O'Brien

## History

### 2.25.0

- Fix codec detection for images that are not JPEG, PNG, GIF or BMP
- In more recent versions of iOS, more image types require the complete image data to detect the image type instead of just the headers
- This mostly just affects very small images, larger images generally were never affected
- This regressed our codec detection logic for images that do not also have TIPs "magic numbers" image type detection
- This fixes that by informing the codecs if the data being provided for detection is the complete image data or not
- Caveat: for images other than JPEG, PNG, GIF, BMP or WEBP, it is likely that it will take the complete image data to detect those images now which can lengthen the duration for book-keeping overhead as the image is being loaded
- If you want to have an image format detected faster than what Core Graphics detects (all data required for most formats), you can either provide a custom codec with better format detection logic or you can update the magic numbers APIs in `TIPImageTypes.m`

### 2.24.3

- Fix scaling logic to better preserve source size aspect ratio during scale
- For example: 800x800 scaled to fill 954x954 would yield a 953x954 size. Now it will properly yield 954x954.

### 2.24.2

- Fix WebP decoder for animations
Expand Down
10 changes: 5 additions & 5 deletions COCOAPODS.md
Expand Up @@ -6,7 +6,7 @@ To integrate TIP into your iOS project using CocoaPods, simply add the following

```ruby
target 'MyApp' do
pod 'TwitterImagePipeline', '~> 2.24.2'
pod 'TwitterImagePipeline', '~> 2.25.0'
end
```

Expand All @@ -23,13 +23,13 @@ If you wish to include these codecs, modify your **Podfile** to define the appro

```ruby
target 'MyApp' do
pod 'TwitterImagePipeline', '~> 2.24.2', :subspecs => ['WebPCodec/Default']
pod 'TwitterImagePipeline', '~> 2.25.0', :subspecs => ['WebPCodec/Default']

pod 'TwitterImagePipeline', '~> 2.24.2', :subspecs => ['WebPCodec/Animated']
pod 'TwitterImagePipeline', '~> 2.25.0', :subspecs => ['WebPCodec/Animated']

pod 'TwitterImagePipeline', '~> 2.24.2', :subspecs => ['MP4Codec']
pod 'TwitterImagePipeline', '~> 2.25.0', :subspecs => ['MP4Codec']

pod 'TwitterImagePipeline', '~> 2.24.2', :subspecs => ['WebPCodec/Animated', 'MP4']
pod 'TwitterImagePipeline', '~> 2.25.0', :subspecs => ['WebPCodec/Animated', 'MP4']
end
```

Expand Down
1 change: 1 addition & 0 deletions Extended/TIPXMP4Codec.m
Expand Up @@ -398,6 +398,7 @@ - (instancetype)initWithDefaultDecoderConfig:(nullable id<TIPXMP4DecoderConfig>)
}

- (TIPImageDecoderDetectionResult)tip_detectDecodableData:(NSData *)data
isCompleteData:(BOOL)complete
earlyGuessImageType:(nullable NSString *)imageType
{
if (data.length < kSignatureDataRequiredToCheck) {
Expand Down
1 change: 1 addition & 0 deletions Extended/TIPXWebPCodec.m
Expand Up @@ -119,6 +119,7 @@ + (BOOL)hasAnimationDecoding
@implementation TIPXWebPDecoder

- (TIPImageDecoderDetectionResult)tip_detectDecodableData:(NSData *)data
isCompleteData:(BOOL)complete
earlyGuessImageType:(nullable NSString *)imageType
{
// RIFF layout is:
Expand Down
4 changes: 2 additions & 2 deletions TwitterImagePipeline.podspec
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = 'TwitterImagePipeline'
s.version = '2.24.2'
s.compiler_flags = '-DTIP_PROJECT_VERSION=2.24'
s.version = '2.25.0'
s.compiler_flags = '-DTIP_PROJECT_VERSION=2.25'
s.summary = 'Twitter Image Pipeline is a robust and performant image loading and caching framework for iOS'
s.description = 'Twitter created a framework for image loading/caching in order to fulfill the numerous needs of Twitter for iOS including being fast, safe, modular and versatile.'
s.homepage = 'https://github.com/twitter/ios-twitter-image-pipeline'
Expand Down
22 changes: 18 additions & 4 deletions TwitterImagePipeline/Project/TIPDefaultImageCodecs.m
Expand Up @@ -15,6 +15,7 @@
#import "TIPDefaultImageCodecs.h"
#import "TIPError.h"
#import "TIPImageContainer.h"
#import "TIPImageTypes.h"
#import "UIImage+TIPAdditions.h"

NS_ASSUME_NONNULL_BEGIN
Expand Down Expand Up @@ -222,18 +223,27 @@ - (instancetype)initWithUTType:(NSString *)UTType
}

- (TIPImageDecoderDetectionResult)tip_detectDecodableData:(NSData *)data
isCompleteData:(BOOL)complete
earlyGuessImageType:(nullable NSString *)imageType
{
if (!imageType) {
imageType = TIPDetectImageType(data, NULL, NULL, NO);
imageType = TIPDetectImageType(data, NULL, NULL, complete);
}

NSString *UTType = TIPImageTypeToUTType(imageType);
if (UTType && UTTypeConformsTo((__bridge CFStringRef)UTType, (__bridge CFStringRef)_UTType)) {
return TIPImageDecoderDetectionResultMatch;
}

return TIPImageDecoderDetectionResultNoMatch;
if (data.length >= TIPMagicNumbersForImageTypeMaximumLength) {
NSString *codecImageType = TIPImageTypeFromUTType(_UTType);
if ([TIPDetectableImageTypesViaMagicNumbers() containsObject:codecImageType]) {
// We have enough data but magic numbers didn't find it for this codec's well defined image type
return TIPImageDecoderDetectionResultNoMatch;
}
}

return (complete) ? TIPImageDecoderDetectionResultNoMatch : TIPImageDecoderDetectionResultNeedMoreData;
}

- (id<TIPImageDecoderContext>)tip_initiateDecoding:(nullable id __unused)config
Expand Down Expand Up @@ -641,7 +651,7 @@ - (nullable TIPImageContainer *)_tip_generateImageWithChunk:(NSData *)chunk
}
} else {
// Animated always updates the source as data flows in,
// so only update the source for progressive/normal
// so only update the source for progressive/normal static images
[self _tip_updateImageSource:chunk didComplete:complete];
}

Expand Down Expand Up @@ -742,7 +752,11 @@ - (void)_tip_attemptToLoadMoreImage:(inout TIPImageDecoderAppendResult * __nonnu
{
const NSUInteger lastFrameCount = _frameCount;
if (_tip_isAnimated) {
[self _tip_updateImageSource:_data didComplete:complete];
if (!_flags.didMakeFinalUpdate || !complete) {
// If we can continue updating the image source w/ more recent data, do so
// i.e. avoid redundant updates with the completed image data which would log a warning
[self _tip_updateImageSource:_data didComplete:complete];
}
BOOL canUpdateFrameCount;
if (tip_available_ios_11) {
// We want to avoid decoding the animation data here in case it conflicts with
Expand Down
11 changes: 7 additions & 4 deletions TwitterImagePipeline/Project/TIPPartialImage.m
Expand Up @@ -281,7 +281,7 @@ - (BOOL)appendData:(NSData *)data final:(BOOL)final
if (!_codecDetectionImageSource) {
TIPAssert(!_codecDetectionBuffer);

if ([self _quickDetectCodecFromData:data]) {
if ([self _quickDetectCodecFromData:data final:final]) {
TIPAssert(_detectedCodec != nil);
return YES;
}
Expand All @@ -299,7 +299,7 @@ - (BOOL)appendData:(NSData *)data final:(BOOL)final
}
CGImageSourceUpdateData(_codecDetectionImageSource, (CFDataRef)_codecDetectionBuffer, final);

[self _fullDetectCodec];
[self _fullDetectCodec:final];
return _detectedCodec != nil;
}

Expand All @@ -310,13 +310,14 @@ - (void)dealloc
}
}

- (BOOL)_quickDetectCodecFromData:(NSData *)data TIP_OBJC_DIRECT
- (BOOL)_quickDetectCodecFromData:(NSData *)data final:(BOOL)final TIP_OBJC_DIRECT
{
NSString *quickDetectType = TIPDetectImageTypeViaMagicNumbers(data);
if (quickDetectType) {
id<TIPImageCodec> quickCodec = [TIPImageCodecCatalogue sharedInstance][quickDetectType];
if (quickCodec) {
TIPImageDecoderDetectionResult result = [quickCodec.tip_decoder tip_detectDecodableData:data
isCompleteData:final
earlyGuessImageType:quickDetectType];
if (TIPImageDecoderDetectionResultMatch == result) {
_detectedCodec = quickCodec;
Expand All @@ -328,7 +329,7 @@ - (BOOL)_quickDetectCodecFromData:(NSData *)data TIP_OBJC_DIRECT
return NO;
}

- (void)_fullDetectCodec TIP_OBJC_DIRECT
- (void)_fullDetectCodec:(BOOL)final TIP_OBJC_DIRECT
{
TIPAssert(_codecDetectionImageSource != nil);
if (_detectedCodec || _potentialCodecs.count == 0) {
Expand All @@ -345,6 +346,7 @@ - (void)_fullDetectCodec TIP_OBJC_DIRECT
if (matchingImageTypeCodec) {
TIPImageDecoderDetectionResult result;
result = [matchingImageTypeCodec.tip_decoder tip_detectDecodableData:_codecDetectionBuffer
isCompleteData:final
earlyGuessImageType:detectedImageType];
if (TIPImageDecoderDetectionResultMatch == result) {
_detectedCodec = matchingImageTypeCodec;
Expand All @@ -362,6 +364,7 @@ - (void)_fullDetectCodec TIP_OBJC_DIRECT
[_potentialCodecs enumerateKeysAndObjectsUsingBlock:^(NSString *imageType, id<TIPImageCodec> codec, BOOL *stop) {
TIPImageDecoderDetectionResult result;
result = [codec.tip_decoder tip_detectDecodableData:self->_codecDetectionBuffer
isCompleteData:final
earlyGuessImageType:detectedImageType];
if (TIPImageDecoderDetectionResultMatch == result) {
self->_detectedCodec = codec;
Expand Down
52 changes: 44 additions & 8 deletions TwitterImagePipeline/Project/TIP_Project.m
Expand Up @@ -97,16 +97,30 @@ CGSize TIPScaleToFillKeepingAspectRatio(CGSize sourceSize, CGSize targetSize, CG
const CGSize scaledSourceSize = CGSizeMake(__tg_ceil(sourceSize.width * scale), __tg_ceil(sourceSize.height * scale));
const CGFloat rx = scaledTargetSize.width / scaledSourceSize.width;
const CGFloat ry = scaledTargetSize.height / scaledSourceSize.height;

CGSize size;
if (rx > ry) {
// cap width to scaled target size's width
// and floor the larger dimension (height)
size = CGSizeMake((MIN(__tg_ceil(scaledSourceSize.width * rx), scaledTargetSize.width) / scale), (__tg_floor(scaledSourceSize.height * rx) / scale));
// Width will be the scaled target width
const CGFloat targetWidth = scaledTargetSize.width;

// get the height from the width preserving aspect-ratio of source
const CGFloat ar = scaledSourceSize.height / scaledSourceSize.width;
const CGFloat aspectHeight = targetWidth * ar;
const CGFloat targetHeight = round(aspectHeight);

size = CGSizeMake(targetWidth / scale, targetHeight / scale);
} else {
// cap width to scaled target size's width
// and floor the larger dimension (height)
size = CGSizeMake((__tg_floor(scaledSourceSize.width * ry) / scale), (MIN(__tg_ceil(scaledSourceSize.height * ry), scaledTargetSize.height) / scale));
// Height will be the scaled target height
const CGFloat targetHeight = scaledTargetSize.height;

// get the width from the height preserving aspect-ratio of source
const CGFloat ar = scaledSourceSize.width / scaledSourceSize.height;
const CGFloat aspectWidth = targetHeight * ar;
const CGFloat targetWidth = round(aspectWidth);

size = CGSizeMake(targetWidth / scale, targetHeight / scale);
}

return size;
}

Expand All @@ -116,8 +130,30 @@ CGSize TIPScaleToFitKeepingAspectRatio(CGSize sourceSize, CGSize targetSize, CGF
const CGSize scaledSourceSize = CGSizeMake(__tg_ceil(sourceSize.width * scale), __tg_ceil(sourceSize.height * scale));
const CGFloat rx = scaledTargetSize.width / scaledSourceSize.width;
const CGFloat ry = scaledTargetSize.height / scaledSourceSize.height;
const CGFloat ratio = MIN(rx, ry);
const CGSize size = CGSizeMake((MIN(__tg_ceil(scaledSourceSize.width * ratio), scaledTargetSize.width) / scale), (MIN(__tg_ceil(scaledSourceSize.height * ratio), scaledTargetSize.height) / scale));

CGSize size;
if (rx < ry) {
// Width will be the scaled target width
const CGFloat targetWidth = scaledTargetSize.width;

// get the height from the width preserving aspect-ratio of source
const CGFloat ar = scaledSourceSize.height / scaledSourceSize.width;
const CGFloat aspectHeight = targetWidth * ar;
const CGFloat targetHeight = round(aspectHeight);

size = CGSizeMake(targetWidth / scale, targetHeight / scale);
} else {
// Height will be the scaled target height
const CGFloat targetHeight = scaledTargetSize.height;

// get the width from the height preserving aspect-ratio of source
const CGFloat ar = scaledSourceSize.width / scaledSourceSize.height;
const CGFloat aspectWidth = targetHeight * ar;
const CGFloat targetWidth = round(aspectWidth);

size = CGSizeMake(targetWidth / scale, targetHeight / scale);
}

return size;
}

Expand Down
2 changes: 2 additions & 0 deletions TwitterImagePipeline/TIPImageCodecs.h
Expand Up @@ -203,10 +203,12 @@ typedef NS_ENUM(NSInteger, TIPImageDecoderRenderMode)
/**
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;

/**
Expand Down
1 change: 1 addition & 0 deletions TwitterImagePipeline/TIPImageCodecs.m
Expand Up @@ -47,6 +47,7 @@
#pragma clang diagnostic pop
} else {
const TIPImageDecoderDetectionResult result = [decoder tip_detectDecodableData:imageData
isCompleteData:YES
earlyGuessImageType:earlyGuessImageType];
if (TIPImageDecoderDetectionResultMatch == result) {
id<TIPImageDecoderContext> context = [decoder tip_initiateDecoding:config
Expand Down
6 changes: 6 additions & 0 deletions TwitterImagePipeline/TIPImageTypes.h
Expand Up @@ -262,6 +262,12 @@ FOUNDATION_EXTERN NSString * __nullable TIPDetectImageTypeFromFile(NSURL *filePa
*/
FOUNDATION_EXTERN NSString * __nullable TIPDetectImageTypeViaMagicNumbers(NSData *data);

/**
What types are detectable via magic numbers?
@return the set of image types that can be detected via magic numbers.
*/
FOUNDATION_EXTERN NSSet<NSString *> * TIPDetectableImageTypesViaMagicNumbers(void);

/**
Detect the number of progressive scans in the provided `NSData`.
Currently only supports progressive JPEG.
Expand Down

0 comments on commit c55cb76

Please sign in to comment.