上周兴冲冲的在炫轮的iOS版中加了梦寐以求的GIF制作功能,这个功能两年前我就像加,只是当时胆子不够大,觉得iOS不能从相册读取GIF,所以这个功能就一直处于被砍掉的状态.
Not Important
用UIImagePickerViewController从图库中选择一个图片,这个百度一下,教程是有很多的,就不在这个本来就不重要的地方说了.
关键就在于如何处理选择的图片
在UIImagePickerViewController的delegate的回调方法中,有个info
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *, id> *)info
这个info可厉害了,里面有很多很多的东西
UIKIT_EXTERN NSString *const UIImagePickerControllerMediaType
UIKIT_EXTERN NSString *const UIImagePickerControllerOriginalImage //原图 UIImage,不过GIF只有第一帧
UIKIT_EXTERN NSString *const UIImagePickerControllerEditedImage
UIKIT_EXTERN NSString *const UIImagePickerControllerCropRect
UIKIT_EXTERN NSString *const UIImagePickerControllerMediaURL UIKIT_EXTERN NSString *const UIImagePickerControllerReferenceURL //原图的文件地址
UIKIT_EXTERN NSString *const UIImagePickerControllerMediaMetadata
UIKIT_EXTERN NSString *const UIImagePickerControllerLivePhoto
直接使用UIImagePickerControllerOriginalImage
获取UIImage肯定是不行的,这就是一个静图,所以我们使用UIImagePickerControllerReferenceURL
获取到这个图片的位置,然后再获取到数据,然后再解析成GIF~完美
于是我就用NSData获取了这个url,然后发现,这个操作得到的NSData是nil!
我仔细一看这个url,是AssetLibrary
的链接,不是一个绝对路径!
看来需要操作AssetLibrary了……
AssetLibrary && Photos
于是我就去Apple Developer官网找AssetLibrary的资料,吃鲸的发现,AssetLibrary已经灭绝(废弃)了!
这是天大的好消息啊,因为查看了其他的资料,据说AssetLibrary是非ARC的,还有一堆同步异步等乱七八糟的东西,总之就是坑很多.现在用Photos来代替,我紧张的去查询Photos,担心他要求的系统SDK比我当前部署的SDK高.
iOS 8 !
哈哈哈哈,和我当前的部署版本是一样的,这就意味着,这个版本的炫轮app的GIF功能是可以对所有用户开放的!
Photos依然有很多异步操作(毕竟有的图片可能是在iCloud上面的),所以我就干脆定义了一个block来
typedef void(^LoadingAssetBlock)(NSArray * images);
我是为了获取GIF的,那么这个返回的参数给个数组,就刚刚好了~
下面我们来根据url获取GIF吧!
- (void)imagesForURL:(NSURL *)url andBlock:(LoadingAssetBlock)block {
PHFetchResult *assets= [PHAsset fetchAssetsWithALAssetURLs:@[url] options:nil]; // 1
PHAsset *asset=[assets objectAtIndex:0]; // 2
[[PHImageManager defaultManager] requestImageDataForAsset:asset options:nil resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) { //3
NSData *data=imageData;
NSString *type=[NSData sd_contentTypeForImageData:data]; //这是SDWebImage的大佬们写的
if ([type isEqualToString:@"image/gif"]) {
UIImage *img = [UIImage sd_animatedGIFWithData:data]; 这也是SDWebImage的大佬们写的
NSMutableArray *renderedImage=[NSMutableArray array];
for (UIImage *item in img.images) {
VKMutableImage *mutable=[VKMutableImage imageWithImage:item];
/**
*这里真的不重要
*/
[renderedImage addObject:mutable];
}
block([NSArray arrayWithArray:renderedImage]);
} else{
UIImage *image=[UIImage imageWithData:imageData];
VKMutableImage *mutable=[VKMutableImage imageWithImage:image];
/**
*这里真的不重要
*/
block(@[mutable]);
}
}];
}
其实只要看注释 1,2,3就够了……
They are not important!
我发现我好像跑题了,这一片的主要内容是,获取UIImage上的各个点的颜色的,然后我们使用RGB数组制作一个UIImage
再再介绍一下背景吧~
按理说上面说的这个图片操作的功能,早在炫轮刚面世的时候就存在的,为啥现在才来说这个东西呢?
因为从一开始就写错了!直到现在才发现
就当我上周兴冲冲的写完iOS的GIF制作功能之后,我就开始写android版的GIF制作功能,花了3天才终于把功能做完了!
正当我准备发一波朋友圈得瑟得瑟的时候,我发现了一个很严重的问题…
使用了一样的调色,为什么结果不一样
!按照以前的习惯,我一定是会怀疑Android版本出现了问题的,可是根据原图,我总觉得,Android上面的结果好像是正确的!
那就意味着,发布了快3
年的炫轮iOS版App
存在一个致命的图像处理的问题!
Here we go
正片开始了~
经过一连串debug手法,我找到了出问题的代码位置:以下是有问题的代码
CGImageRef imageRef=originImage.CGImage;
CGContextRef context = newBitmapRGBA8ContextFromImage(imageRef);
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
CGRect rect = CGRectMake(0, 0, width, height);
CGContextDrawImage(context, rect, imageRef);
unsigned char *bitmapData = (unsigned char *)CGBitmapContextGetData(context);
size_t bytesPerRow = CGBitmapContextGetBytesPerRow(context);
size_t bufferLength = bytesPerRow * height;
unsigned char *newBitmap = NULL;
if(bitmapData) {
newBitmap = (unsigned char *)malloc(sizeof(unsigned char) * bytesPerRow * height);
if(newBitmap) {
for(int i = 0; i +3 < bufferLength;i=i+3) {
int R=bitmapData[i];
int G=bitmapData[i+1];
int B=bitmapData[i+2];
/**
* 这里一点儿也不重要
*/
newBitmap[i] = R;
newBitmap[i+1]=G;
newBitmap[i+2]=B;
newBitmap[i+3]=255;
}
}
free(bitmapData);
} else {
NSLog(@"Error getting bitmap pixel data\n");
}
CGContextRelease(context);
tempImage=[PublicHelper convertBitmapRGBA8ToUIImage:newBitmap withWidth:(int)width withHeight:(int)height];
free(newBitmap);
return tempImage;
newBitmap就是经过处理的图片的RGBA数组
关键错误应该是在这里:
int R=bitmapData[i];
int G=bitmapData[i+1];
int B=bitmapData[i+2];
虽然说我们知道图片是由一堆RGBA构成的,但是我们并不知道RGBA的顺序呀~这并不像Android里面那么爽,Color是一个32bit(4字节)的数字(int),每个字节分别代表着ARGB
那么iOS呢?
查了很多的资料,看到各位大佬都说iOS上面是RGBA,那么我原来的写法,好像很RGBA啊,好像没什么问题啊~
little-endian,big-endian
难道说,和大小端有关??
虽然说是RGBA
,但是存在数组里面的是ABGR
?
抱着试一试和重构的态度,借鉴了各种大佬的代码,之后改成了这样
const int imageWidth = originImage.size.width;
const int imageHeight = originImage.size.height;
size_t bytesPerRow = imageWidth * 4;
uint32_t* rgbImageBuf = (uint32_t*)malloc(bytesPerRow * imageHeight);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(rgbImageBuf, imageWidth, imageHeight, 8, bytesPerRow, colorSpace,kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipLast);
CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), originImage.CGImage);
int pixelNum = imageWidth * imageHeight;
uint32_t* pCurPtr = rgbImageBuf;
for (int i = 0; i < pixelNum; i++, pCurPtr++)
{
uint8_t *ptr = (uint8_t*)pCurPtr;
ptr[0] // Alpha
ptr[1] // Blue
ptr[2] // Greem
ptr[3] // Red
/**
* 这里一点儿也不重要
*/
}
CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, rgbImageBuf, bytesPerRow * imageHeight,ProviderReleaseData);
CGImageRef imageRef = CGImageCreate(imageWidth, imageHeight, 8, 32, bytesPerRow, colorSpace,kCGImageAlphaLast | kCGBitmapByteOrder32Little, dataProvider,NULL, true, kCGRenderingIntentDefault);
CGDataProviderRelease(dataProvider);
tempImage = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
return tempImage;
void ProviderReleaseData (void *info, const void *data, size_t size)
{
free((void*)data);
}
现在用uint32_t来类比android中的Color
还用了DataProvider来把字节流转换成UIImage~这样修改后的代码,变得更加简洁了!
关键是,现在工作的正常了!
End
不打不相识,Android和iOS不一起开发互相比较的话,就不能互相进步~
好了,这次不仅添加了GIF制作这个功能,还把以前的图片改色功能给修好了~真是一举两得啊!
演示视频
iOS这个视频里面的颜色修改,还没有改~.~