上周兴冲冲的在炫轮的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 GIF ScreenShot
iOS GIF ScreenShot

使用了一样的调色,为什么结果不一样!按照以前的习惯,我一定是会怀疑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~这样修改后的代码,变得更加简洁了!

关键是,现在工作的正常了!

iOS GIF Right

End

不打不相识,Android和iOS不一起开发互相比较的话,就不能互相进步~

好了,这次不仅添加了GIF制作这个功能,还把以前的图片改色功能给修好了~真是一举两得啊!

演示视频

iOS<=====>Android

iOS这个视频里面的颜色修改,还没有改~.~