diff --git a/README.md b/README.md index 6ddcc17..be10218 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,7 @@ Multi image downloader with priority in Swift - Caching images - Pure Swift - Composable image -- Support webp - - Now supported by Carthage only. See [SwiftWebP](https://github.com/jinSasaki/SwiftWebP). +- Support WebP Single download | Multi download with priority --- | --- @@ -83,11 +82,11 @@ imageView.vl_setImage(urls: [ ``` ### WebP image -Add `SwiftWebP.framework`. +If you installed via Carthage, add `SwiftWebP.framework`. ```swift import Vulcan -import SwiftWebP +import SwiftWebP // Only installed via Carthage extension WebPDecoder: ImageDecoder { public func decode(data: Data, response: HTTPURLResponse, options: ImageDecodeOptions?) throws -> Image { diff --git a/Sources/WebP/UIImage+WebP.swift b/Sources/WebP/UIImage+WebP.swift new file mode 100644 index 0000000..4797021 --- /dev/null +++ b/Sources/WebP/UIImage+WebP.swift @@ -0,0 +1,18 @@ +// +// UIImage+WebP.swift +// SwiftWebP +// +// Created by Jin Sasaki on 2016/11/04. +// Copyright © 2016年 Sasakky. All rights reserved. +// + +import UIKit + +public extension UIImage { + public class func image(fromWebPData webpData: Data) -> UIImage? { + guard let image = WebPDecoder.decode(webpData) else { + return nil + } + return image + } +} diff --git a/Sources/WebP/WebPDecoder.h b/Sources/WebP/WebPDecoder.h new file mode 100644 index 0000000..9f5a41e --- /dev/null +++ b/Sources/WebP/WebPDecoder.h @@ -0,0 +1,12 @@ +// +// WebPDecoder.h +// +// Created by Jin Sasaki on 2016/11/04. +// Copyright © 2016年 Sasakky. All rights reserved. +// + +#import + +@interface WebPDecoder : NSObject ++ (nullable UIImage *)decode:(nullable NSData *)data; +@end diff --git a/Sources/WebP/WebPDecoder.m b/Sources/WebP/WebPDecoder.m new file mode 100644 index 0000000..14d72f5 --- /dev/null +++ b/Sources/WebP/WebPDecoder.m @@ -0,0 +1,152 @@ +// +// WebPDecoder.m +// +// Created by Jin Sasaki on 2016/11/04. +// Copyright © 2016年 Sasakky. All rights reserved. +// + +#import "WebPDecoder.h" + +#import "webp/decode.h" +#import "webp/mux_types.h" +#import "webp/demux.h" + +static void FreeImageData(void *info, const void *data, size_t size) { + free((void *)data); +} + +@implementation WebPDecoder ++ (nullable UIImage *)decode:(nullable NSData *)data +{ + if (!data) { + return nil; + } + + WebPData webpData; + WebPDataInit(&webpData); + webpData.bytes = data.bytes; + webpData.size = data.length; + WebPDemuxer *demuxer = WebPDemux(&webpData); + if (!demuxer) { + return nil; + } + + uint32_t flags = WebPDemuxGetI(demuxer, WEBP_FF_FORMAT_FLAGS); + if (!(flags & ANIMATION_FLAG)) { + // for static single webp image + UIImage *staticImage = [self rawWepImageWithData:webpData]; + WebPDemuxDelete(demuxer); + return staticImage; + } + + WebPIterator iter; + if (!WebPDemuxGetFrame(demuxer, 1, &iter)) { + WebPDemuxReleaseIterator(&iter); + WebPDemuxDelete(demuxer); + return nil; + } + + NSMutableArray *images = [NSMutableArray array]; + NSTimeInterval duration = 0; + + do { + UIImage *image; + if (iter.blend_method == WEBP_MUX_BLEND) { + image = [self blendWebpImageWithOriginImage:[images lastObject] iterator:iter]; + } else { + image = [self rawWepImageWithData:iter.fragment]; + } + + if (!image) { + continue; + } + + [images addObject:image]; + duration += iter.duration / 1000.0f; + + } while (WebPDemuxNextFrame(&iter)); + + WebPDemuxReleaseIterator(&iter); + WebPDemuxDelete(demuxer); + + UIImage *finalImage = nil; + finalImage = [UIImage animatedImageWithImages:images duration:duration]; + return finalImage; +} + + ++ (nullable UIImage *)blendWebpImageWithOriginImage:(nullable UIImage *)originImage iterator:(WebPIterator)iter { + if (!originImage) { + return nil; + } + + CGSize size = originImage.size; + CGFloat tmpX = iter.x_offset; + CGFloat tmpY = size.height - iter.height - iter.y_offset; + CGRect imageRect = CGRectMake(tmpX, tmpY, iter.width, iter.height); + + UIImage *image = [self rawWepImageWithData:iter.fragment]; + if (!image) { + return nil; + } + + CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB(); + uint32_t bitmapInfo = iter.has_alpha ? kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast : 0; + CGContextRef blendCanvas = CGBitmapContextCreate(NULL, size.width, size.height, 8, 0, colorSpaceRef, bitmapInfo); + CGContextDrawImage(blendCanvas, CGRectMake(0, 0, size.width, size.height), originImage.CGImage); + CGContextDrawImage(blendCanvas, imageRect, image.CGImage); + CGImageRef newImageRef = CGBitmapContextCreateImage(blendCanvas); + + image = [UIImage imageWithCGImage:newImageRef]; + + CGImageRelease(newImageRef); + CGContextRelease(blendCanvas); + CGColorSpaceRelease(colorSpaceRef); + + return image; +} + ++ (nullable UIImage *)rawWepImageWithData:(WebPData)webpData { + WebPDecoderConfig config; + if (!WebPInitDecoderConfig(&config)) { + return nil; + } + + if (WebPGetFeatures(webpData.bytes, webpData.size, &config.input) != VP8_STATUS_OK) { + return nil; + } + + config.output.colorspace = config.input.has_alpha ? MODE_rgbA : MODE_RGB; + config.options.use_threads = 1; + + // Decode the WebP image data into a RGBA value array. + if (WebPDecode(webpData.bytes, webpData.size, &config) != VP8_STATUS_OK) { + return nil; + } + + int width = config.input.width; + int height = config.input.height; + if (config.options.use_scaling) { + width = config.options.scaled_width; + height = config.options.scaled_height; + } + + // Construct a UIImage from the decoded RGBA value array. + CGDataProviderRef provider = + CGDataProviderCreateWithData(NULL, config.output.u.RGBA.rgba, config.output.u.RGBA.size, FreeImageData); + CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB(); + CGBitmapInfo bitmapInfo = config.input.has_alpha ? kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast : 0; + size_t components = config.input.has_alpha ? 4 : 3; + CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault; + CGImageRef imageRef = CGImageCreate(width, height, 8, components * 8, components * width, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent); + + CGColorSpaceRelease(colorSpaceRef); + CGDataProviderRelease(provider); + + UIImage *image = [[UIImage alloc] initWithCGImage:imageRef]; + CGImageRelease(imageRef); + + return image; +} + +@end diff --git a/Vulcan.podspec b/Vulcan.podspec index 439cb69..d776d25 100644 --- a/Vulcan.podspec +++ b/Vulcan.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'Vulcan' - s.version = '0.1.2' + s.version = '0.2.0' s.license = 'MIT' s.summary = 'Multi image downloader with priority in Swift' s.homepage = 'https://github.com/jinSasaki/Vulcan' @@ -12,4 +12,13 @@ Pod::Spec.new do |s| s.tvos.deployment_target = '9.0' s.source_files = 'Sources/**/*.swift' + + s.subspec 'WebP' do |webp| + webp.source_files = 'Sources/WebP/*.{h,m,swift}' + webp.xcconfig = { + 'USER_HEADER_SEARCH_PATHS' => '$(inherited) $(SRCROOT)/libwebp/src' + } + webp.dependency 'libwebp' +end + end