基于 NSData 的图片压缩

栏目: IOS · 发布时间: 6年前

内容简介:当前网上找到的图片压缩方法大部分都是基于 UIImage 的,但是如果要支持 gif 的话,那么从相册读出来就得是 NSData,就必须要基于 NSData 来做压缩了。主要用到了1、通过

当前网上找到的图片压缩方法大部分都是基于 UIImage 的,但是如果要支持 gif 的话,那么从相册读出来就得是 NSData,就必须要基于 NSData 来做压缩了。

主要用到了 <ImageIO><CoreGraphics> 库。 步骤就是

1. `CGImageSource` 从 data读出 `CGImageSourceRef` 对象;
2.  `CGImageSourceRef` 中读取 `CGImageRef`;
3. 经过 旋转、调整大小、压缩质量;
4. 然后用 `CGImageDestination` 重新写入新的`data`。
复制代码

一、 静态图片,并转为 jpg

1、通过 CGImageSourceRef 读取数据

//通过CFData读取文件的数据
    CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
    //获取文件的帧数
    size_t count = CGImageSourceGetCount(source);
    if (count == 0) {
        CFRelease(source);
        return nil;
    }
复制代码

2、 读取 每一帧的属性,一般 source 的 count 都是1, GifLive Photo 的都是大于1的。

// 图像属性
    CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, 0, nil);
    //图像的旋转方向
    CGImagePropertyOrientation orientation = (uint32_t)[(__bridge NSNumber *)CFDictionaryGetValue(cfFrameProperties, kCGImagePropertyOrientation) integerValue];
    if (orientation == 0) { // 方向丢失
        orientation = kCGImagePropertyOrientationUp;
    }
复制代码

3、读取到 CGImageRef

imageRef = CGImageSourceCreateImageAtIndex(source, 0, NULL);
复制代码

4、处理 大小,旋转

#pragma mark  调整大小,自动旋转,减少分开的计算而已
CGImageRef DDCreateResizeAndUpOrientationImageRef(CGImageRef imageRef, CGSize targetSize, CGImagePropertyOrientation currentOrientation)
{
    size_t width = targetSize.width;
    size_t height = targetSize.height;
    // orientation 如果是左右的话,那么 size 要反一下,否则 宽高不对。
    switch (currentOrientation) {
        case kCGImagePropertyOrientationLeft:
        case kCGImagePropertyOrientationLeftMirrored:
        case kCGImagePropertyOrientationRight:
        case kCGImagePropertyOrientationRightMirrored:
        {
            size_t temp = width;
            width = height;
            height = temp;
        }
            break;
        default:
            break;
    }
    
    CGColorSpaceRef space = CGImageGetColorSpace(imageRef);
    CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
    if((bitmapInfo == kCGImageAlphaLast) || (bitmapInfo == kCGImageAlphaNone))
        bitmapInfo = (CGBitmapInfo)kCGImageAlphaNoneSkipLast;
    size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
//    size_t bytesPerRow = width * 4;
    size_t bytesPerRow = 0;
    // create context
    CGContextRef context = CGBitmapContextCreate(NULL,
                                                 width,
                                                 height,
                                                 bitsPerComponent,
                                                 bytesPerRow,
                                                 space,
                                                 bitmapInfo);
    CGColorSpaceRelease(space); // release space
    if (!context) {
        return nil;
    }
    CGContextSetInterpolationQuality(context, kCGInterpolationMedium);
    if (currentOrientation != kCGImagePropertyOrientationUp) {
        // 需要旋转 图片
        CGAffineTransform transform = TransformForOrientation(currentOrientation, CGSizeMake(width, height));
        CGContextConcatCTM(context, transform);
        
        CGRect transposedRect = CGRectMake(0, 0, height, width);
        CGContextDrawImage(context, transposedRect, imageRef);
    } else {
        CGRect newRect = CGRectIntegral(CGRectMake(0, 0, width, height));
        CGContextDrawImage(context, newRect, imageRef);
    }
    
    // get new imageRef
    CGImageRef decoded = CGBitmapContextCreateImage(context);
    CFRelease(context);
    if (!decoded) {
        return nil;
    }
    return decoded;
}
复制代码

5、调整质量

通过 CGImageDestinationRef 写入 data,设置 kCGImageDestinationLossyCompressionQuality 参数来调整质量

参数主要是前面读取的 CGImageRef 的属性,这里因为不想重新读,所以就当参数传递了。

在 这里之前,需要 重新设置 图片的 orientation,否则结果 orientation 会丢失,造成旋转失效

// 修改参数 orientation,如果上面修改了 orientation,则这里也必须修改,否则会丢失新旋转。
    CFMutableDictionaryRef mutDicRef = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, cfFrameProperties);
    CGFloat width = newSize.width;
    CGFloat height = newSize.height;
    CFNumberRef widthNum = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &width);
    CFNumberRef heightNum = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &height);
    CFDictionarySetValue(mutDicRef, kCGImagePropertyPixelWidth, widthNum);
    CFDictionarySetValue(mutDicRef, kCGImagePropertyPixelHeight, heightNum);
    
    CGImagePropertyOrientation newOrientation = kCGImagePropertyOrientationUp;
    CFNumberRef newOrientationNum = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &newOrientation);
    CFDictionarySetValue(mutDicRef, kCGImagePropertyOrientation, newOrientationNum);
复制代码
+ (NSData *)getCompressImageDataWIthImageRef:(CGImageRef)imageRef uttype:(CFStringRef)uttype properties:(CFDictionaryRef)properties isToJPG:(BOOL)isToJPG toMaxBytes:(NSUInteger)maxBytes
{
    // 转为 jpg,并压缩质量
    CFStringRef type = nil;
    if (isToJPG) {
        type = kUTTypeJPEG;
    } else if (uttype) {
        type = CFStringCreateCopy(kCFAllocatorDefault, uttype);
    } else {
        if (@available(iOS 9.0, *)) {
            type = CGImageGetUTType(imageRef);
        }
    }
    BOOL success = NO;
    NSMutableData *mutableData = nil;
    do {
        mutableData = [NSMutableData data];
        // destination 的 type 是确定最终 image 的类型。
        CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)mutableData, type, 1, NULL);
        CFMutableDictionaryRef mutDicRef = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, properties);
        float c = 0.5;
        CFNumberRef compressionQuality = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &c);
        CFDictionarySetValue(mutDicRef, kCGImageDestinationLossyCompressionQuality, compressionQuality);
        CGImageDestinationAddImage(destination, imageRef, mutDicRef);
        success = CGImageDestinationFinalize(destination);
        
        CFRelease(destination);
        CFRelease(mutDicRef);
        CFRelease(compressionQuality);
        
        if (!success) {
            NSDictionary *userInfo = @{
                                       NSLocalizedDescriptionKey: NSLocalizedString(@"Could not finalize image destination", nil)
                                       };
            NSLog(@"resizePNGImageDataToSize %@",userInfo);
            break;
        }
    } while (mutableData.length > maxBytes);
    
    CFRelease(type);
    if (!success) {
        return nil;
    }
    return mutableData;
}
复制代码

6、完整代码

#pragma mark - 压缩 静态图,并转为 JPG
+ (NSData *)thumbnailImageData:(NSData *)imageData fitSize:(CGSize)targetSize maxBytes:(double)maxBytes minBytes:(double)minBytes toJPG:(BOOL)isToJPG
{
    if (targetSize.width == 0 || targetSize.height == 0) {
        return imageData;
    }
    
    // < 30k 也算了,不转了
    if (imageData.length < minBytes) {
        return imageData;
    }
    CGSize size = [UIImage sizeFromImageData:imageData];
    // size 不超,bytes 也不超,也不转了
    if (size.width <= targetSize.width && size.height <= targetSize.height &&
        imageData.length <= maxBytes) {
        return imageData;
    }
    //通过CFData读取文件的数据
    CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
    //获取文件的帧数
    size_t count = CGImageSourceGetCount(source);
    if (count == 0) {
        CFRelease(source);
        return nil;
    }
    
    // 计算 size
    CGSize newSize = [self fitJPGImageSize:size toSize:targetSize];
    
    // 图像属性
    CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, 0, nil);
    //图像的旋转方向
    CGImagePropertyOrientation orientation = (uint32_t)[(__bridge NSNumber *)CFDictionaryGetValue(cfFrameProperties, kCGImagePropertyOrientation) integerValue];
    if (orientation == 0) { // 方向丢失
        orientation = kCGImagePropertyOrientationUp;
    }
    // 创建第一个 imageRef
    CGImageRef imageRef = NULL;
    imageRef = CGImageSourceCreateImageAtIndex(source, 0, NULL);
    if (!imageRef) {
        CFRelease(source);
        return nil;
    }
    // 1. 旋转
    // 2. 调整大小
    if (!CGSizeEqualToSize(newSize, size)) { // 需要压缩大小 或者 需要旋转,进行重绘
        CGImageRef resizeImageRef = DDCreateResizeAndUpOrientationImageRef(imageRef, newSize, orientation);
        if (resizeImageRef) {
            
            CGImageRelease(imageRef);
            imageRef = NULL;
            imageRef = CGImageCreateCopy(resizeImageRef);
            CGImageRelease(resizeImageRef);
        }
    } else if (orientation != kCGImagePropertyOrientationUp){
        // 旋转
        CGImageRef rotateRef = DDCreateRotateImageRef(imageRef, orientation);
        if (rotateRef) {
            CGImageRelease(imageRef);
            imageRef = CGImageCreateCopy(rotateRef);
            CGImageRelease(rotateRef);
        }
    }
    // 3. 转为 jpg,并压缩质量
    CFStringRef type = CGImageSourceGetType(source);
    if (isToJPG) {
        type = kUTTypeJPEG;
    }
    
    // 修改参数 orientation,如果上面修改了 orientation,则这里也必须修改,否则会丢失新旋转。
    CFMutableDictionaryRef mutDicRef = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, cfFrameProperties);
    CGFloat width = newSize.width;
    CGFloat height = newSize.height;
    CFNumberRef widthNum = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &width);
    CFNumberRef heightNum = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &height);
    CFDictionarySetValue(mutDicRef, kCGImagePropertyPixelWidth, widthNum);
    CFDictionarySetValue(mutDicRef, kCGImagePropertyPixelHeight, heightNum);
    
    CGImagePropertyOrientation newOrientation = kCGImagePropertyOrientationUp;
    CFNumberRef newOrientationNum = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &newOrientation);
    CFDictionarySetValue(mutDicRef, kCGImagePropertyOrientation, newOrientationNum);
    
    
    NSData *mutableData = [self getCompressImageDataWIthImageRef:imageRef uttype:type properties:mutDicRef isToJPG:isToJPG toMaxBytes:maxBytes];
    
    CFRelease(cfFrameProperties);
    CGImageRelease(imageRef);
    CFRelease(type);
    CFRelease(source);
    
    return mutableData;
}

复制代码

二、 gif

gif 的处理类似,主要就是循环遍历 CGImageSourceRef 里的 CGImageRef ,但是gif 每一帧的图片可能不一样,所以压缩质量不太好处理,只处理了像素大小。

+ (NSData *)thumbnailGIFImageData:(NSData *)gifData fitSize:(CGSize)targetSize maxBytes:(double)maxBytes
{
    if (targetSize.width == 0 || targetSize.height == 0) {
        return gifData;
    }
    // resize new size
    CGSize size = [UIImage sizeFromImageData:gifData];
    
    if (size.width <= targetSize.width && size.height <= targetSize.height && gifData.length <= maxBytes) {
        return gifData;
    }
    // 计算新的大小
    CGSize newSize = [NSData fitGifImageSize:size toSize:targetSize];
    return [NSData resizeGifImageData:gifData toSize:newSize];
    
}
/// gif 每一帧的图片大小应该稍微有点区别,不太一样,所以只调整 size,不压缩质量
+ (NSData *)resizeGifImageData:(NSData *)gifData toSize:(CGSize)targetSize
{
    if (targetSize.width == 0 || targetSize.height == 0) {
        return gifData;
    }
    //通过CFData读取gif文件的数据
    CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)gifData, NULL);
    //获取gif文件的帧数
    size_t count = CGImageSourceGetCount(source);
    //设置gif播放的时间
    NSTimeInterval duration = 0.0f;
    
    CFDictionaryRef imageProperties = CGImageSourceCopyProperties(source, nil);
    NSLog(@"\n CGImageSourceCopyProperties %@ \n",imageProperties);
    
    // gif data properties
    NSMutableData *mutableData = [NSMutableData data];
    CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)mutableData, kUTTypeGIF, count, NULL);
    
    CFDictionaryRef gifProperties;
    BOOL result = CFDictionaryGetValueIfPresent(imageProperties, kCGImagePropertyGIFDictionary, (const void **)&gifProperties);
    if (result) {
        CGImageDestinationSetProperties(destination, gifProperties);
    }
    CFRelease(imageProperties);
    for (size_t i = 0; i < count; i++) {
        //获取gif指定帧的像素位图
        CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, i, NULL);
        if (!imageRef) {
            continue;
        }
        //获取每张图的播放时间
        float frameDuration = [NSData frameDurationAtIndex:i source:source];
        duration += frameDuration;
        
        //
        CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, i, nil);
        //图像的旋转方向
        CGImagePropertyOrientation orientation = (uint32_t)[(__bridge NSNumber *)CFDictionaryGetValue(cfFrameProperties, kCGImagePropertyOrientation) integerValue];
        if (orientation == 0) { // 方向丢失
            orientation = kCGImagePropertyOrientationUp;
        }
        
        CGImageRef newImageRef = DDCreateResizeAndUpOrientationImageRef(imageRef, targetSize, orientation);
        
        // add quality
        CFMutableDictionaryRef mutDicRef = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, cfFrameProperties);
        
        float c = 0.5;
        CFNumberRef compressionQuality = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &c);
        CFDictionarySetValue(mutDicRef, kCGImageDestinationLossyCompressionQuality, compressionQuality);
        // orientation 必须修改,否则会丢失 旋转。
        CGImagePropertyOrientation newOrientation = kCGImagePropertyOrientationUp;
        CFNumberRef newOrientationNum = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &newOrientation);
        CFDictionarySetValue(mutDicRef, kCGImagePropertyOrientation, newOrientationNum);
        
        // modify size
        CGFloat width = targetSize.width;
        CGFloat height = targetSize.height;
        CFNumberRef widthNum = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &width);
        CFNumberRef heightNum = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &height);
        CFDictionarySetValue(mutDicRef, kCGImagePropertyPixelWidth, widthNum);
        CFDictionarySetValue(mutDicRef, kCGImagePropertyPixelHeight, heightNum);
        
        NSLog(@" mutDicRef \n %@ \n",mutDicRef);
        CGImageDestinationAddImage(destination, newImageRef, mutDicRef);
        CFRelease(cfFrameProperties);
        CGImageRelease(newImageRef);
        CGImageRelease(imageRef);
        CFRelease(mutDicRef);
        CFRelease(compressionQuality);
        newImageRef = NULL;
        imageRef = NULL;
        mutDicRef = NULL;
    }
    
    
    BOOL success = CGImageDestinationFinalize(destination);
    CFRelease(destination);
    
    if (!success) {
        NSDictionary *userInfo = @{
                     NSLocalizedDescriptionKey: NSLocalizedString(@"Could not finalize image destination", nil)
                     };
        NSLog(@"resizeGifImageDataToSize %@",userInfo);
        CFRelease(source);
        return nil;
    }
    CFRelease(source);
    if (mutableData.length > gifData.length) {
        return gifData;
    }
    return [NSData dataWithData:mutableData];
}

#pragma mark gif 每一帧的 播放时间
+ (float)frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source {
    float frameDuration = 0.1f;
    //获取这一帧图片的属性字典
    CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil);
    NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties;
    //获取gif属性字典
    NSDictionary *gifProperties = frameProperties[(NSString *)kCGImagePropertyGIFDictionary];
    //获取这一帧持续的时间
    NSNumber *delayTimeUnclampedProp = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime];
    if (delayTimeUnclampedProp) {
        frameDuration = [delayTimeUnclampedProp floatValue];
    }
    else {
        NSNumber *delayTimeProp = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime];
        if (delayTimeProp) {
            frameDuration = [delayTimeProp floatValue];
        }
    }
    //如果帧数小于0.1,则指定为0.1
    if (frameDuration < 0.011f) {
        frameDuration = 0.100f;
    }
    CFRelease(cfFrameProperties);
    return frameDuration;
}
复制代码

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Docker从入门到实战

Docker从入门到实战

黄靖钧 / 机械工业出版社 / 2017-6 / 69.00元

本书从Docker的相关概念与基础知识讲起,结合实际应用,通过不同开发环境的实战例子,详细介绍了Docker的基础知识与进阶实战的相关内容,以引领读者快速入门并提高。 本书共19章,分3篇。第1篇容器技术与Docker概念,涵盖的内容有容器技术、Docker简介、安装Docker等。第2篇Docker基础知识,涵盖的内容有Docker基础、Docker镜像、Dockerfile文件、Dock......一起来看看 《Docker从入门到实战》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具