123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520 |
- //
- // SLImageDecoder.m
- // SLImageView
- //
- // Created by 王双龙 on 2018/10/26.
- // Copyright © 2018年 https://www.jianshu.com/u/e15d1f644bea. All rights reserved.
- //
- #import "SLImageDecoder.h"
- #import <pthread.h>
- #import <webp/decode.h>
- #import <webp/encode.h>
- #import <webp/demux.h>
- #import <webp/mux.h>
- //来源YYImage
- #define SL_FOUR_CC(c1,c2,c3,c4) ((uint32_t)(((c4) << 24) | ((c3) << 16) | ((c2) << 8) | (c1)))
- #define SL_TWO_CC(c1,c2) ((uint16_t)(((c2) << 8) | (c1)))
- @implementation SLImageFrame
- @end
- @interface SLImageDecoder () {
- CGImageSourceRef _imageSource; //图像源数据
- WebPDemuxer *_webpSource; //webp格式图片的源数据 https://developers.google.com/speed/webp/docs/api
- }
- @end
- @implementation SLImageDecoder
- - (void)decoderWithData:(NSData *)data scale:(CGFloat)scale{
- _data = data;
- _scale = scale;
- _loopCount = 1;
- _totalTime = 0;
- _frameCount = 1;
- _imageType = imageDataType(_data);
- [self startDecoder];
- }
- - (void)startDecoder{
- [self imageFrameCount];
- switch (_imageType) {
- case SLImageTypeGIF:
- _loopCount = [self loopCountForGifData:_data];
- break;
- case SLImageTypeWebP:
- _loopCount = [self loopCountForGifData:_data];
- break;
- case SLImageTypePNG:
- _loopCount = [self loopCountForGifData:_data];
- break;
- default:
- break;
- }
- }
- #pragma mark - 图片信息
- static void SLCGDataProviderReleaseDataCallback(void *info, const void *data, size_t size) {
- if (info) free(info);
- }
- CGColorSpaceRef SLCGColorSpaceGetDeviceRGB() {
- static CGColorSpaceRef space;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- space = CGColorSpaceCreateDeviceRGB();
- });
- return space;
- }
- /**
- 返回图片的类型
- */
- NSInteger imageDataType(NSData * data){
-
- CFDataRef dataRef = (__bridge CFDataRef)data;
- if (!data) return SLImageTypeUnknown;
- uint64_t length = CFDataGetLength(dataRef);
- if (length < 16) return SLImageTypeUnknown;
-
- const char *bytes = (char *)CFDataGetBytePtr(dataRef);
-
- uint32_t magic4 = *((uint32_t *)bytes);
- switch (magic4) {
- case SL_FOUR_CC(0x4D, 0x4D, 0x00, 0x2A): { // big endian TIFF
- return SLImageTypeTIFF;
- } break;
-
- case SL_FOUR_CC(0x49, 0x49, 0x2A, 0x00): { // little endian TIFF
- return SLImageTypeTIFF;
- } break;
-
- case SL_FOUR_CC(0x00, 0x00, 0x01, 0x00): { // ICO
- return SLImageTypeICO;
- } break;
-
- case SL_FOUR_CC(0x00, 0x00, 0x02, 0x00): { // CUR
- return SLImageTypeICO;
- } break;
-
- case SL_FOUR_CC('i', 'c', 'n', 's'): { // ICNS
- return SLImageTypeICNS;
- } break;
-
- case SL_FOUR_CC('G', 'I', 'F', '8'): { // GIF
- return SLImageTypeGIF;
- } break;
-
- case SL_FOUR_CC(0x89, 'P', 'N', 'G'): { // PNG
- uint32_t tmp = *((uint32_t *)(bytes + 4));
- if (tmp == SL_FOUR_CC('\r', '\n', 0x1A, '\n')) {
- return SLImageTypePNG;
- }
- } break;
-
- case SL_FOUR_CC('R', 'I', 'F', 'F'): { // WebP
- uint32_t tmp = *((uint32_t *)(bytes + 8));
- if (tmp == SL_FOUR_CC('W', 'E', 'B', 'P')) {
- return SLImageTypeWebP;
- }
- } break;
- }
-
- uint16_t magic2 = *((uint16_t *)bytes);
- switch (magic2) {
- case SL_TWO_CC('B', 'A'):
- case SL_TWO_CC('B', 'M'):
- case SL_TWO_CC('I', 'C'):
- case SL_TWO_CC('P', 'I'):
- case SL_TWO_CC('C', 'I'):
- case SL_TWO_CC('C', 'P'): { // BMP
- return SLImageTypeBMP;
- }
- case SL_TWO_CC(0xFF, 0x4F): { // JPEG2000
- return SLImageTypeJPEG2000;
- }
- }
-
- // JPG FF D8 FF
- if (memcmp(bytes,"\377\330\377",3) == 0) return SLImageTypeJPEG;
- // JP2
- if (memcmp(bytes + 4, "\152\120\040\040\015", 5) == 0) return SLImageTypeJPEG2000;
-
- return SLImageTypeUnknown;
- };
- /**
- 每一帧图片的方向
- */
- UIImageOrientation SLUIImageOrientationFromEXIFValue(NSInteger value) {
- switch (value) {
- case kCGImagePropertyOrientationUp: return UIImageOrientationUp;
- case kCGImagePropertyOrientationDown: return UIImageOrientationDown;
- case kCGImagePropertyOrientationLeft: return UIImageOrientationLeft;
- case kCGImagePropertyOrientationRight: return UIImageOrientationRight;
- case kCGImagePropertyOrientationUpMirrored: return UIImageOrientationUpMirrored;
- case kCGImagePropertyOrientationDownMirrored: return UIImageOrientationDownMirrored;
- case kCGImagePropertyOrientationLeftMirrored: return UIImageOrientationLeftMirrored;
- case kCGImagePropertyOrientationRightMirrored: return UIImageOrientationRightMirrored;
- default: return UIImageOrientationUp;
- }
- }
- /**
- 获取每一帧图片解压缩后的位图
- */
- CGImageRef SLCGImageCreateDecodedCopy(CGImageRef imageRef, BOOL decodeForDisplay) {
- if (!imageRef) return NULL;
- size_t width = CGImageGetWidth(imageRef);
- size_t height = CGImageGetHeight(imageRef);
- if (width == 0 || height == 0) return NULL;
-
- if (decodeForDisplay) { //decode with redraw (may lose some precision)
- CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef) & kCGBitmapAlphaInfoMask;
- BOOL hasAlpha = NO;
- if (alphaInfo == kCGImageAlphaPremultipliedLast ||
- alphaInfo == kCGImageAlphaPremultipliedFirst ||
- alphaInfo == kCGImageAlphaLast ||
- alphaInfo == kCGImageAlphaFirst) {
- hasAlpha = YES;
- }
- // BGRA8888 (premultiplied) or BGRX8888
- // same as UIGraphicsBeginImageContext() and -[UIView drawRect:]
- CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
- bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
- CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, SLCGColorSpaceGetDeviceRGB(), bitmapInfo);
- if (!context) return NULL;
- CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); // decode
- CGImageRef newImage = CGBitmapContextCreateImage(context);
- CFRelease(context);
- return newImage;
-
- } else {
- CGColorSpaceRef space = CGImageGetColorSpace(imageRef);
- size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
- size_t bitsPerPixel = CGImageGetBitsPerPixel(imageRef);
- size_t bytesPerRow = CGImageGetBytesPerRow(imageRef);
- CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
- if (bytesPerRow == 0 || width == 0 || height == 0) return NULL;
-
- CGDataProviderRef dataProvider = CGImageGetDataProvider(imageRef);
- if (!dataProvider) return NULL;
- CFDataRef data = CGDataProviderCopyData(dataProvider); // decode
- if (!data) return NULL;
-
- CGDataProviderRef newProvider = CGDataProviderCreateWithCFData(data);
- CFRelease(data);
- if (!newProvider) return NULL;
-
- CGImageRef newImage = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, space, bitmapInfo, newProvider, NULL, false, kCGRenderingIntentDefault);
- CFRelease(newProvider);
- return newImage;
- }
- }
- /**
- 获取动图的图片帧数
- */
- - (void)imageFrameCount{
- if(_imageType == SLImageTypeWebP) {
- if (_webpSource) WebPDemuxDelete(_webpSource);
- _webpSource = NULL;
- WebPData webPData = {0};
- webPData.bytes = _data.bytes;
- webPData.size = _data.length;
- WebPDemuxer *demuxer = WebPDemux(&webPData);
- if (!demuxer) _frameCount = 1;
- _webpSource = demuxer;
- NSInteger frameCout = WebPDemuxGetI(demuxer, WEBP_FF_FRAME_COUNT);
- _frameCount = frameCout;
- } else {
- //将GIF图片转换成对应的图片源
- _imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)_data, NULL);
- //获取其中图片源个数,即由多少帧图片组成
- size_t frameCout = CGImageSourceGetCount(_imageSource);
- _frameCount = frameCout;
- }
- }
- /**
- 获取某一帧的图片信息:索引、持续时长、宽高、方向、解码后的image
- index >= 0
- */
- - (SLImageFrame *)imageFrameAtIndex:(NSInteger)index{
-
- if(index >= _frameCount || index < 0) return nil;
-
- SLImageFrame * imageFrame = [[SLImageFrame alloc] init];
- imageFrame.index = index;
- CGFloat width = 0, height = 0;
- NSInteger orientation = UIImageOrientationUp;
- if(_imageType == SLImageTypeWebP) {
- WebPIterator iter = {0};
- if (WebPDemuxGetFrame(_webpSource, (int)index, &iter)) {
- imageFrame.index = index;
- imageFrame.duration = iter.duration / 1000.0;
- imageFrame.width = iter.width;
- imageFrame.height = iter.height;
- imageFrame.imageOrientation = UIImageOrientationUp;
- imageFrame.image = [self imageAtIndex:index];
- imageFrame.offsetX = iter.x_offset;
- imageFrame.offsetY = self.canvasSize.height - iter.y_offset - iter.height;
- };
- WebPDemuxReleaseIterator(&iter);
- }else{
-
- // image属性
- CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(_imageSource, index, NULL);
- imageFrame.duration = [self imageDurationAtIndex:index];
-
- CFTypeRef valueWidth = CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelWidth);
- if (valueWidth){
- CFNumberGetValue(valueWidth, kCFNumberCGFloatType, &width);
- imageFrame.width = width;
- }
- CFTypeRef valueHeight = CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelHeight);
- if (valueHeight){
- CFNumberGetValue(valueHeight, kCFNumberCGFloatType, &height);
- imageFrame.height = height;
- }
- CFTypeRef valueOrientation = CFDictionaryGetValue(imageProperties, kCGImagePropertyOrientation);
- if (valueOrientation) {
- CFNumberGetValue(valueOrientation, kCFNumberNSIntegerType, &orientation);
- imageFrame.imageOrientation = SLUIImageOrientationFromEXIFValue(orientation);
- }
- imageFrame.image = [self imageAtIndex:index];
- CFRelease(imageProperties);
- }
- return imageFrame;
- }
- /**
- 获取某一帧image
- */
- - (UIImage *)imageAtIndex:(NSInteger)index{
-
- if(index >= _frameCount || index < 0) return nil;
-
- CGImageRef imageRef;
-
- if(_imageType == SLImageTypeWebP) {
-
- WebPIterator iter;
- if (!WebPDemuxGetFrame(_webpSource, (int)(index), &iter)) return NULL; // demux webp frame data
- // frame numbers are one-based in webp -----------^
-
- int frameWidth = iter.width;
- int frameHeight = iter.height;
- if (frameWidth < 1 || frameHeight < 1) return NULL;
-
- int width = frameWidth;
- int height = frameHeight;
-
- const uint8_t *payload = iter.fragment.bytes;
- size_t payloadSize = iter.fragment.size;
-
- WebPDecoderConfig config;
- if (!WebPInitDecoderConfig(&config)) {
- WebPDemuxReleaseIterator(&iter);
- return NULL;
- }
- if (WebPGetFeatures(payload , payloadSize, &config.input) != VP8_STATUS_OK) {
- WebPDemuxReleaseIterator(&iter);
- return NULL;
- }
-
- size_t bitsPerComponent = 8;
- size_t bitsPerPixel = 32;
-
- size_t bytesPerRow = ((bitsPerPixel / 8 * width + (32 - 1)) / 32) * 32;
- size_t length = bytesPerRow * height;
- CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst; //bgrA
-
- void *pixels = calloc(1, length);
- if (!pixels) {
- WebPDemuxReleaseIterator(&iter);
- return NULL;
- }
-
- config.output.colorspace = MODE_bgrA;
- config.output.is_external_memory = 1;
- config.output.u.RGBA.rgba = pixels;
- config.output.u.RGBA.stride = (int)bytesPerRow;
- config.output.u.RGBA.size = length;
- VP8StatusCode result = WebPDecode(payload, payloadSize, &config); // decode
- if ((result != VP8_STATUS_OK) && (result != VP8_STATUS_NOT_ENOUGH_DATA)) {
- WebPDemuxReleaseIterator(&iter);
- free(pixels);
- return NULL;
- }
- WebPDemuxReleaseIterator(&iter);
-
- // if (extendToCanvas && (iter.x_offset != 0 || iter.y_offset != 0)) {
- // void *tmp = calloc(1, length);
- // if (tmp) {
- // vImage_Buffer src = {pixels, height, width, bytesPerRow};
- // vImage_Buffer dest = {tmp, height, width, bytesPerRow};
- // vImage_CGAffineTransform transform = {1, 0, 0, 1, iter.x_offset, -iter.y_offset};
- // uint8_t backColor[4] = {0};
- // vImage_Error error = vImageAffineWarpCG_ARGB8888(&src, &dest, NULL, &transform, backColor, kvImageBackgroundColorFill);
- // if (error == kvImageNoError) {
- // memcpy(pixels, tmp, length);
- // }
- // free(tmp);
- // }
- // }
-
- CGDataProviderRef provider = CGDataProviderCreateWithData(pixels, pixels, length, SLCGDataProviderReleaseDataCallback);
- if (!provider) {
- free(pixels);
- return NULL;
- }
- pixels = NULL; // hold by provider
-
- imageRef = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, SLCGColorSpaceGetDeviceRGB(), bitmapInfo, provider, NULL, false, kCGRenderingIntentDefault);
- CFRelease(provider);
-
- } else {
- //从GIF图片中取出某一帧源图片 https://www.jianshu.com/p/e9843d5b70a2
- imageRef = CGImageSourceCreateImageAtIndex(_imageSource, index, (CFDictionaryRef)@{(id)kCGImageSourceShouldCache:@(NO),(id)kCGImageSourceShouldCacheImmediately:@(NO)}); //默认会缓存到内存,和imageNamed一样;这里我们选择不缓存,减少内存占用
- }
-
- //将图片源解码后转换成UIImageView能直接使用的图片源NO
- CGImageRef imageRefDecoded = SLCGImageCreateDecodedCopy(imageRef, YES);
- if (imageRefDecoded) {
- CFRelease(imageRef);
- imageRef = imageRefDecoded;
- }
- UIImage* image = [UIImage imageWithCGImage:imageRef scale:_scale orientation:UIImageOrientationUp];
- if(imageRef){
- CFRelease(imageRef);
- }
- return image;
- }
- /**
- 某一帧持续时长
- */
- - (NSTimeInterval)imageDurationAtIndex:(NSUInteger)index{
-
- if(index >= _frameCount || index < 0) return 0;
-
- NSTimeInterval duration = 0;
-
- if(_imageType == SLImageTypeWebP) {
- WebPIterator iter = {0};
- if (WebPDemuxGetFrame(_webpSource, (int)index, &iter)) {
- duration = iter.duration / 1000.0;
- };
- WebPDemuxReleaseIterator(&iter);
- }else{
- // image属性
- CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(_imageSource, index, NULL);
- if (imageProperties) {
- CFDictionaryRef gifProperties;
- BOOL result = CFDictionaryGetValueIfPresent(imageProperties, kCGImagePropertyGIFDictionary, (const void **)&gifProperties);
- if (result) {
- const void *durationValue;
- if (CFDictionaryGetValueIfPresent(gifProperties, kCGImagePropertyGIFUnclampedDelayTime, &durationValue)) {
- duration = [(__bridge NSNumber *)durationValue doubleValue];
- if (duration < 0) {
- if (CFDictionaryGetValueIfPresent(gifProperties, kCGImagePropertyGIFDelayTime, &durationValue)) {
- duration = [(__bridge NSNumber *)durationValue doubleValue];
- }
- }
- }
- }
- }
- CFRelease(imageProperties);
- }
-
- // http://nullsleep.tumblr.com/post/16524517190/animated-gif-minimum-frame-delay-browser-compatibility
- if (duration < 0.02) duration = 0.1;
- return duration;
- }
- /**
- 返回gif文件的循环次数 0表示无限循环
- */
- - (NSInteger)loopCountForGifData:(NSData *)data{
-
- NSInteger loopCount = 1;
-
- if (_imageType == SLImageTypeWebP) {
- loopCount = WebPDemuxGetI(_webpSource, WEBP_FF_LOOP_COUNT);
- } else {
- //gif相关属性
- CFDictionaryRef properties = CGImageSourceCopyProperties(_imageSource, NULL);
- if (properties) {
- CFDictionaryRef gif = CFDictionaryGetValue(properties, kCGImagePropertyGIFDictionary);
- if (gif) {
- CFTypeRef loop = CFDictionaryGetValue(gif, kCGImagePropertyGIFLoopCount);
- if (loop) {
- //如果loop == NULL,表示不循环播放,当loopCount == 0时,表示无限循环;
- CFNumberGetValue(loop, kCFNumberNSIntegerType, &loopCount);
- if(loopCount == 0){
- // NSLog(@"无限循环播放");
- }else{
- // NSLog(@"循环次数:%ld ",loopCount);
- }
- }else{
- // NSLog(@"循环一次");
- };
- }
- }
- CFRelease(properties);
- }
- return loopCount;
- }
- /**
- 循环一次所需的总时长
- */
- - (NSTimeInterval)totalTimeOfLoopOnce{
- NSTimeInterval duration = 0;
- @autoreleasepool {
- for(int i = 0; i < _frameCount; i++){
- duration += [self imageDurationAtIndex:i];
- }
- }
- return duration;
- }
- #pragma mark - Getter
- - (CGSize)canvasSize{
- if(_imageType == SLImageTypeWebP) {
- CGFloat width = WebPDemuxGetI(_webpSource, WEBP_FF_CANVAS_WIDTH);
- CGFloat height = WebPDemuxGetI(_webpSource, WEBP_FF_CANVAS_HEIGHT);
- return CGSizeMake(width, height);
- }else{
- // image属性
- CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(_imageSource, 0, NULL);
- NSDictionary *properties = (__bridge NSDictionary *)imageProperties;
- CGFloat width = [properties[(NSString *)kCGImagePropertyPixelWidth] floatValue];
- CGFloat height = [properties[(NSString *)kCGImagePropertyPixelHeight] floatValue];
- CFRelease(imageProperties);
- return CGSizeMake(width, height);
- }
- }
- - (NSTimeInterval)totalTime{
- if (_totalTime == 0) {
- _totalTime = [self totalTimeOfLoopOnce];
- }
- return _totalTime;
- }
- - (void)dealloc{
- if (_imageSource) {
- CFRelease(_imageSource);
- }
- if (_webpSource) {
- WebPDemuxDelete(_webpSource);
- }
- }
- @end
|