当前网上找到的图片压缩方法大部分都是基于 UIImage 的,可是若是要支持 gif 的话,那么从相册读出来就得是 NSData,就必需要基于 NSData 来作压缩了。html
主要用到了 <ImageIO>
和 <CoreGraphics>
库。 步骤就是c++
1. `CGImageSource` 从 data读出 `CGImageSourceRef` 对象;
2. `CGImageSourceRef` 中读取 `CGImageRef`;
3. 通过 旋转、调整大小、压缩质量;
4. 而后用 `CGImageDestination` 从新写入新的`data`。
复制代码
一、经过 CGImageSourceRef
读取数据git
//经过CFData读取文件的数据
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
//获取文件的帧数
size_t count = CGImageSourceGetCount(source);
if (count == 0) {
CFRelease(source);
return nil;
}
复制代码
二、 读取 每一帧的属性,通常 source 的 count 都是1,Gif
和 Live Photo
的都是大于1的。github
// 图像属性
CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, 0, nil);
//图像的旋转方向
CGImagePropertyOrientation orientation = (uint32_t)[(__bridge NSNumber *)CFDictionaryGetValue(cfFrameProperties, kCGImagePropertyOrientation) integerValue];
if (orientation == 0) { // 方向丢失
orientation = kCGImagePropertyOrientationUp;
}
复制代码
三、读取到 CGImageRef
segmentfault
imageRef = CGImageSourceCreateImageAtIndex(source, 0, NULL);
复制代码
四、处理 大小,旋转bash
#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;
}
复制代码
五、调整质量ui
经过 CGImageDestinationRef
写入 data,设置 kCGImageDestinationLossyCompressionQuality
参数来调整质量spa
参数主要是前面读取的CGImageRef
的属性,这里由于不想从新读,因此就当参数传递了。.net
在 这里以前,须要 从新设置 图片的 orientation,不然结果 orientation 会丢失,形成旋转失效code
// 修改参数 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;
}
复制代码
六、完整代码
#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 的处理相似,主要就是循环遍历 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;
}
复制代码
介绍一个 gif 处理的库,用 c++ 写的,处理 gif 质量比较好,
ImageOption 里面处理 gif 用的就是 gifsicle
一、www.jianshu.com/p/154b938d2…
三、www.jianshu.com/p/ea0274a33…
四、www.jianshu.com/p/2345afeab…
六、www.jianshu.com/p/af34e85b9…