diff --git a/Cartfile b/Cartfile index 156ab0a..8ee2568 100644 --- a/Cartfile +++ b/Cartfile @@ -1,2 +1,2 @@ -github "SDWebImage/SDWebImage" ~> 5.5 +github "SDWebImage/SDWebImage" ~> 5.7 github "SDWebImage/libwebp-Xcode" ~> 1.0 diff --git a/Cartfile.resolved b/Cartfile.resolved index 76a844b..6dc0632 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,2 +1,2 @@ -github "SDWebImage/SDWebImage" "5.5.0" +github "SDWebImage/SDWebImage" "5.7.0" github "SDWebImage/libwebp-Xcode" "1.1.0" diff --git a/Package.swift b/Package.swift index 756ea73..78c55d1 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.5.0"), + .package(url: "https://github.com/SDWebImage/SDWebImage.git", from: "5.7.0"), .package(url: "https://github.com/SDWebImage/libwebp-Xcode.git", from: "1.1.0") ], targets: [ diff --git a/SDWebImageWebPCoder.podspec b/SDWebImageWebPCoder.podspec index 56316f5..7f29ea7 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.5' + s.dependency 'SDWebImage/Core', '~> 5.7' s.dependency 'libwebp', '~> 1.0' end diff --git a/SDWebImageWebPCoder/Classes/SDImageWebPCoder.m b/SDWebImageWebPCoder/Classes/SDImageWebPCoder.m index acc5634..e05fa1f 100644 --- a/SDWebImageWebPCoder/Classes/SDImageWebPCoder.m +++ b/SDWebImageWebPCoder/Classes/SDImageWebPCoder.m @@ -609,12 +609,16 @@ static CGSize SDCalculateThumbnailSize(CGSize fullSize, BOOL preserveAspectRatio if (options[SDImageCoderEncodeCompressionQuality]) { compressionQuality = [options[SDImageCoderEncodeCompressionQuality] doubleValue]; } + NSUInteger maxFileSize = 0; + if (options[SDImageCoderEncodeMaxFileSize]) { + maxFileSize = [options[SDImageCoderEncodeMaxFileSize] unsignedIntegerValue]; + } NSArray *frames = [SDImageCoderHelper framesFromAnimatedImage:image]; BOOL encodeFirstFrame = [options[SDImageCoderEncodeFirstFrameOnly] boolValue]; if (encodeFirstFrame || frames.count == 0) { // for static single webp image - data = [self sd_encodedWebpDataWithImage:image.CGImage quality:compressionQuality]; + data = [self sd_encodedWebpDataWithImage:image.CGImage quality:compressionQuality fileSize:maxFileSize]; } else { // for animated webp image WebPMux *mux = WebPMuxNew(); @@ -623,7 +627,7 @@ static CGSize SDCalculateThumbnailSize(CGSize fullSize, BOOL preserveAspectRatio } for (size_t i = 0; i < frames.count; i++) { SDImageFrame *currentFrame = frames[i]; - NSData *webpData = [self sd_encodedWebpDataWithImage:currentFrame.image.CGImage quality:compressionQuality]; + NSData *webpData = [self sd_encodedWebpDataWithImage:currentFrame.image.CGImage quality:compressionQuality fileSize:maxFileSize]; int duration = currentFrame.duration * 1000; WebPMuxFrameInfo frame = { .bitstream.bytes = webpData.bytes, .bitstream.size = webpData.length, @@ -660,7 +664,7 @@ static CGSize SDCalculateThumbnailSize(CGSize fullSize, BOOL preserveAspectRatio return data; } -- (nullable NSData *)sd_encodedWebpDataWithImage:(nullable CGImageRef)imageRef quality:(double)quality { +- (nullable NSData *)sd_encodedWebpDataWithImage:(nullable CGImageRef)imageRef quality:(double)quality fileSize:(NSUInteger)fileSize { NSData *webpData; if (!imageRef) { return nil; @@ -704,7 +708,7 @@ static CGSize SDCalculateThumbnailSize(CGSize fullSize, BOOL preserveAspectRatio return nil; } - uint8_t *rgba = NULL; + uint8_t *rgba = NULL; // RGBA Buffer managed by CFData, don't call `free` on it, instead call `CFRelease` on `dataRef` // We could not assume that input CGImage's color mode is always RGB888/RGBA8888. Convert all other cases to target color mode using vImage if (byteOrderNormal && ((alphaInfo == kCGImageAlphaNone) || (alphaInfo == kCGImageAlphaLast))) { // If the input CGImage is already RGB888/RGBA8888 @@ -758,34 +762,58 @@ static CGSize SDCalculateThumbnailSize(CGSize fullSize, BOOL preserveAspectRatio rgba = dest.data; // Converted buffer bytesPerRow = dest.rowBytes; // Converted bytePerRow - CFRelease(dataRef); - dataRef = NULL; + CFRelease(dataRef); // Use CFData to manage bytes for free, the same code path for error handling + dataRef = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, rgba, bytesPerRow * height, kCFAllocatorDefault); } - uint8_t *data = NULL; // Output WebP data float qualityFactor = quality * 100; // WebP quality is 0-100 // Encode RGB888/RGBA8888 buffer to WebP data - size_t size; - if (hasAlpha) { - size = WebPEncodeRGBA(rgba, (int)width, (int)height, (int)bytesPerRow, qualityFactor, &data); - } else { - size = WebPEncodeRGB(rgba, (int)width, (int)height, (int)bytesPerRow, qualityFactor, &data); + // Using the libwebp advanced API: https://developers.google.com/speed/webp/docs/api#advanced_encoding_api + WebPConfig config; + WebPPicture picture; + WebPMemoryWriter writer; + + if (!WebPConfigPreset(&config, WEBP_PRESET_DEFAULT, qualityFactor) || + !WebPPictureInit(&picture)) { + // shouldn't happen, except if system installation is broken + CFRelease(dataRef); + return nil; } - if (dataRef) { - CFRelease(dataRef); // free non-converted rgba buffer - dataRef = NULL; + + config.target_size = (int)fileSize; // Max filesize for output, 0 means use quality instead + config.thread_level = 1; // Thread encoding for fast + config.lossless = 0; // Disable lossless encoding (If we need, can add new Encoding Options in future version) + picture.use_argb = config.lossless; // Lossy encoding use YUV for internel bitstream + picture.width = (int)width; + picture.height = (int)height; + picture.writer = WebPMemoryWrite; // Output in memory data buffer + picture.custom_ptr = &writer; + WebPMemoryWriterInit(&writer); + + int result; + if (hasAlpha) { + result = WebPPictureImportRGBA(&picture, rgba, (int)bytesPerRow); } else { - free(rgba); // free converted rgba buffer - rgba = NULL; + result = WebPPictureImportRGB(&picture, rgba, (int)bytesPerRow); + } + if (!result) { + WebPMemoryWriterClear(&writer); + CFRelease(dataRef); + return nil; } - if (size) { + result = WebPEncode(&config, &picture); + CFRelease(dataRef); // Free bitmap buffer + WebPPictureFree(&picture); + + if (result) { // success - webpData = [NSData dataWithBytes:data length:size]; - } - if (data) { - WebPFree(data); + webpData = [NSData dataWithBytes:writer.mem length:writer.size]; + } else { + // failed + webpData = nil; } + WebPMemoryWriterClear(&writer); return webpData; }