當前網上找到的圖片壓縮方法大部分都是基於 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,Gif
和 Live 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(@"
CGImageSourceCopyProperties %@
",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
%@
",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
參考連結
1、www.jianshu.com/p/154b938d2…
3、www.jianshu.com/p/ea0274a33…
4、www.jianshu.com/p/2345afeab…
6、www.jianshu.com/p/af34e85b9…