diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 0e4f74f..e40cd7a 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -74,27 +74,13 @@ jobs: - name: Test - ${{ matrix.iosDestination }} run: | set -o pipefail - xcodebuild test -workspace "${{ env.WORKSPACE_NAME }}" -scheme "Tests iOS" -destination "${{ matrix.iosDestination }}" -configuration Debug CODE_SIGNING_ALLOWED=NO + xcodebuild test -workspace "${{ env.WORKSPACE_NAME }}" -scheme "SDWebImageWebPCoderTests" -destination "${{ matrix.iosDestination }}" -configuration Debug CODE_SIGNING_ALLOWED=NO mv ~/Library/Developer/Xcode/DerivedData/ ./DerivedData/iOS - - name: Test - ${{ matrix.macOSDestination }} - run: | - set -o pipefail - xcodebuild test -workspace "${{ env.WORKSPACE_NAME }}" -scheme "Tests Mac" -destination "${{ matrix.macOSDestination }}" -configuration Debug CODE_SIGNING_ALLOWED=NO - mv ~/Library/Developer/Xcode/DerivedData/ ./DerivedData/macOS - - - name: Test - ${{ matrix.tvOSDestination }} - run: | - set -o pipefail - xcodebuild test -workspace "${{ env.WORKSPACE_NAME }}" -scheme "Tests TV" -destination "${{ matrix.tvOSDestination }}" -configuration Debug CODE_SIGNING_ALLOWED=NO - mv ~/Library/Developer/Xcode/DerivedData/ ./DerivedData/tvOS - - name: Code Coverage run: | set -o pipefail export PATH="/usr/local/opt/curl/bin:$PATH" curl --version - bash <(curl -s https://codecov.io/bash) -v -D './DerivedData/macOS' -J '^SDWebImageWebPCoder$' -c -X gcov -F macos bash <(curl -s https://codecov.io/bash) -v -D './DerivedData/iOS' -J '^SDWebImageWebPCoder$' -c -X gcov -F ios - bash <(curl -s https://codecov.io/bash) -v -D './DerivedData/tvOS' -J '^SDWebImageWebPCoder$' -c -X gcov -F tvos diff --git a/Cartfile b/Cartfile index f73bcc5..6c3ac23 100644 --- a/Cartfile +++ b/Cartfile @@ -1,2 +1,2 @@ -github "SDWebImage/SDWebImage" ~> 5.15 +github "SDWebImage/SDWebImage" ~> 5.16 github "SDWebImage/libwebp-Xcode" ~> 1.0 diff --git a/Example/SDWebImageWebPCoderExample/ViewController.m b/Example/SDWebImageWebPCoderExample/ViewController.m index 66652f0..73275f8 100644 --- a/Example/SDWebImageWebPCoderExample/ViewController.m +++ b/Example/SDWebImageWebPCoderExample/ViewController.m @@ -37,7 +37,8 @@ NSURL *staticWebPURL = [NSURL URLWithString:@"https://www.gstatic.com/webp/gallery/2.webp"]; NSURL *animatedWebPURL = [NSURL URLWithString:@"http://littlesvr.ca/apng/images/world-cup-2014-42.webp"]; - [self.imageView1 sd_setImageWithURL:staticWebPURL placeholderImage:nil options:0 context:@{SDWebImageContextImageThumbnailPixelSize : @(CGSizeMake(300, 300))} progress:nil completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) { + [self.imageView1 sd_setImageWithURL:staticWebPURL placeholderImage:nil options:0 context:@{SDWebImageContextImageScaleDownLimitBytes : @(1024 * 100)} progress:nil completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) { + NSCAssert(image.size.width < 200, @"Limit Bytes should limit image size to 186"); if (image) { NSLog(@"%@", @"Static WebP load success"); } diff --git a/Package.resolved b/Package.resolved index 3452e00..1f5343b 100644 --- a/Package.resolved +++ b/Package.resolved @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/SDWebImage/SDWebImage.git", "state": { "branch": null, - "revision": "966e6c3ee4569227ce67434d890bb22073ead2d6", - "version": "5.5.0" + "revision": "fb50c1d20f24db5322b2f8f379de3618f75fe08e", + "version": "5.15.5" } } ] diff --git a/Package.swift b/Package.swift index aaa4b85..58f9f50 100644 --- a/Package.swift +++ b/Package.swift @@ -17,7 +17,7 @@ let package = Package( dependencies: [ // Dependencies declare other packages that this package depends on. // .package(url: /* package url */, from: "1.0.0"), - .package(url: "https://github.com/SDWebImage/SDWebImage.git", from: "5.15.0"), + .package(url: "https://github.com/SDWebImage/SDWebImage.git", from: "5.16.0"), .package(url: "https://github.com/SDWebImage/libwebp-Xcode.git", from: "1.1.0") ], targets: [ diff --git a/Podfile b/Podfile index 30eccae..a97e6c6 100644 --- a/Podfile +++ b/Podfile @@ -8,7 +8,6 @@ target 'SDWebImageWebPCoderExample' do platform :ios, '9.0' project example_project_path pod 'SDWebImageWebPCoder', :path => './' - pod 'SDWebImage', :path => '../SDWebImage' end target 'SDWebImageWebPCoderTests' do @@ -16,5 +15,4 @@ target 'SDWebImageWebPCoderTests' do project test_project_path pod 'Expecta' pod 'SDWebImageWebPCoder', :path => './' - pod 'SDWebImage', :path => '../SDWebImage' end diff --git a/SDWebImageWebPCoder.podspec b/SDWebImageWebPCoder.podspec index 1830aa8..ec7eb77 100644 --- a/SDWebImageWebPCoder.podspec +++ b/SDWebImageWebPCoder.podspec @@ -27,7 +27,7 @@ This is a SDWebImage coder plugin to support WebP image. 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SD_WEBP=1 WEBP_USE_INTRINSICS=1', 'USER_HEADER_SEARCH_PATHS' => '$(inherited) $(SRCROOT)/libwebp/src' } - s.dependency 'SDWebImage/Core', '~> 5.15' + s.dependency 'SDWebImage/Core', '~> 5.16' s.dependency 'libwebp', '~> 1.0' end diff --git a/SDWebImageWebPCoder/Classes/SDImageWebPCoder.m b/SDWebImageWebPCoder/Classes/SDImageWebPCoder.m index db308c7..bd158b3 100644 --- a/SDWebImageWebPCoder/Classes/SDImageWebPCoder.m +++ b/SDWebImageWebPCoder/Classes/SDImageWebPCoder.m @@ -88,6 +88,20 @@ static inline CGContextRef _Nullable CreateWebPCanvas(BOOL hasAlpha, CGSize canv return canvas; } +// TODO, share this logic for multiple coders, or do refactory in v6.0 (The coder plugin should provide image information back to Core, like `CGImageSourceCopyPropertiesAtIndex`) +static inline CGSize SDCalculateScaleDownPixelSize(NSUInteger limitBytes, CGSize originalSize, NSUInteger frameCount, NSUInteger bytesPerPixel) { + if (CGSizeEqualToSize(originalSize, CGSizeZero)) return CGSizeMake(1, 1); + NSUInteger totalFramePixelSize = limitBytes / bytesPerPixel / (frameCount ?: 1); + CGFloat ratio = originalSize.height / originalSize.width; + CGFloat width = sqrt(totalFramePixelSize / ratio); + CGFloat height = width * ratio; + width = MAX(1, floor(width)); + height = MAX(1, floor(height)); + CGSize size = CGSizeMake(width, height); + + return size; +} + @interface SDWebPCoderFrame : NSObject @property (nonatomic, assign) NSUInteger index; // Frame index (zero based) @@ -126,6 +140,7 @@ static inline CGContextRef _Nullable CreateWebPCanvas(BOOL hasAlpha, CGSize canv NSUInteger _currentBlendIndex; BOOL _preserveAspectRatio; CGSize _thumbnailSize; + BOOL _limitBytes; } - (void)dealloc { @@ -218,6 +233,24 @@ static inline CGContextRef _Nullable CreateWebPCanvas(BOOL hasAlpha, CGSize canv CGColorSpaceRef colorSpace = [self sd_createColorSpaceWithDemuxer:demuxer]; int canvasWidth = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH); int canvasHeight = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT); + uint32_t frameCount = WebPDemuxGetI(demuxer, WEBP_FF_FRAME_COUNT); + int loopCount = WebPDemuxGetI(demuxer, WEBP_FF_LOOP_COUNT); + + NSUInteger limitBytes = 0; + NSNumber *limitBytesValue = options[SDImageCoderDecodeScaleDownLimitBytes]; + if (limitBytesValue != nil) { + limitBytes = limitBytesValue.unsignedIntegerValue; + } + // Scale down to limit bytes if need + if (limitBytes > 0) { + // Hack 32 BitsPerPixel + CGSize imageSize = CGSizeMake(canvasWidth, canvasHeight); + CGSize framePixelSize = SDCalculateScaleDownPixelSize(limitBytes, imageSize, frameCount, 4); + // Override thumbnail size + thumbnailSize = framePixelSize; + preserveAspectRatio = YES; + } + // Check whether we need to use thumbnail CGSize scaledSize = [SDImageCoderHelper scaledSizeWithImageSize:CGSizeMake(canvasWidth, canvasHeight) scaleSize:thumbnailSize preserveAspectRatio:preserveAspectRatio shouldScaleUp:NO]; @@ -245,7 +278,6 @@ static inline CGContextRef _Nullable CreateWebPCanvas(BOOL hasAlpha, CGSize canv return nil; } - int loopCount = WebPDemuxGetI(demuxer, WEBP_FF_LOOP_COUNT); NSMutableArray *frames = [NSMutableArray array]; do { @@ -312,6 +344,12 @@ static inline CGContextRef _Nullable CreateWebPCanvas(BOOL hasAlpha, CGSize canv preserveAspectRatio = preserveAspectRatioValue.boolValue; } _preserveAspectRatio = preserveAspectRatio; + NSUInteger limitBytes = 0; + NSNumber *limitBytesValue = options[SDImageCoderDecodeScaleDownLimitBytes]; + if (limitBytesValue != nil) { + limitBytes = limitBytesValue.unsignedIntegerValue; + } + _limitBytes = limitBytes; _currentBlendIndex = NSNotFound; SD_LOCK_INIT(_lock); } @@ -352,6 +390,15 @@ static inline CGContextRef _Nullable CreateWebPCanvas(BOOL hasAlpha, CGSize canv [self scanAndCheckFramesValidWithDemuxer:_demux]; } SD_UNLOCK(_lock); + // Scale down to limit bytes if need + if (_limitBytes > 0) { + // Hack 32 BitsPerPixel + CGSize imageSize = CGSizeMake(_canvasWidth, _canvasHeight); + CGSize framePixelSize = SDCalculateScaleDownPixelSize(_limitBytes, imageSize, _frameCount, 4); + // Override thumbnail size + _thumbnailSize = framePixelSize; + _preserveAspectRatio = YES; + } } - (UIImage *)incrementalDecodedImageWithOptions:(SDImageCoderOptions *)options { @@ -911,6 +958,21 @@ static float GetFloatValueForKey(NSDictionary * _Nonnull dictionary, NSString * preserveAspectRatio = preserveAspectRatioValue.boolValue; } _preserveAspectRatio = preserveAspectRatio; + NSUInteger limitBytes = 0; + NSNumber *limitBytesValue = options[SDImageCoderDecodeScaleDownLimitBytes]; + if (limitBytesValue != nil) { + limitBytes = limitBytesValue.unsignedIntegerValue; + } + _limitBytes = limitBytes; + // Scale down to limit bytes if need + if (_limitBytes > 0) { + // Hack 32 BitsPerPixel + CGSize imageSize = CGSizeMake(_canvasWidth, _canvasHeight); + CGSize framePixelSize = SDCalculateScaleDownPixelSize(_limitBytes, imageSize, _frameCount, 4); + // Override thumbnail size + _thumbnailSize = framePixelSize; + _preserveAspectRatio = YES; + } _scale = scale; _demux = demuxer; _imageData = data; diff --git a/Tests/SDWebImageWebPCoderTests.m b/Tests/SDWebImageWebPCoderTests.m index 324f8c7..c2c797f 100644 --- a/Tests/SDWebImageWebPCoderTests.m +++ b/Tests/SDWebImageWebPCoderTests.m @@ -129,6 +129,9 @@ const int64_t kAsyncTestTimeout = 5; XCTestExpectation *expectation = [self expectationWithDescription:@"test SDAnimatedImageView view category"]; SDAnimatedImageView *imageView = [SDAnimatedImageView new]; NSURL *testURL = [NSURL URLWithString:@"http://littlesvr.ca/apng/images/SteamEngine.webp"]; + NSString *key = [SDWebImageManager.sharedManager cacheKeyForURL:testURL]; + [SDImageCache.sharedImageCache removeImageFromMemoryForKey:key]; + [SDImageCache.sharedImageCache removeImageFromDiskForKey:key]; [imageView sd_setImageWithURL:testURL placeholderImage:nil options:SDWebImageProgressiveLoad progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) { dispatch_async(dispatch_get_main_queue(), ^{ UIImage *image = imageView.image; diff --git a/Tests/SDWebImageWebPCoderTests.xcodeproj/project.pbxproj b/Tests/SDWebImageWebPCoderTests.xcodeproj/project.pbxproj index 2b15956..25bd836 100644 --- a/Tests/SDWebImageWebPCoderTests.xcodeproj/project.pbxproj +++ b/Tests/SDWebImageWebPCoderTests.xcodeproj/project.pbxproj @@ -284,8 +284,8 @@ PRODUCT_NAME = SDWebImageWebPCoder; SDKROOT = iphoneos; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; - TARGETED_DEVICE_FAMILY = "1,2"; + SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx appletvsimulator appletvos"; + TARGETED_DEVICE_FAMILY = "1,2,3,4"; TVOS_DEPLOYMENT_TARGET = 9.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -358,8 +358,8 @@ PRODUCT_NAME = SDWebImageWebPCoder; SDKROOT = iphoneos; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; - TARGETED_DEVICE_FAMILY = "1,2"; + SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx appletvsimulator appletvos"; + TARGETED_DEVICE_FAMILY = "1,2,3,4"; TVOS_DEPLOYMENT_TARGET = 9.0; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic";