通过新建立相同的localIdentifier的PHAsset来实现获取到的图片和原图片大小相同,方向正常。
通过TZImagePickerController获取到选择图片的assets和photos。代码如下:
TZImagePickerController *imagePickerVc = [[TZImagePickerController alloc] initWithMaxImagesCount:self.homeParamsEntity.chooseImageEntity.count delegate:self]; // You can get the photos by block, the same as by delegate. // 你可以通过block或者代理,来得到用户选择的照片. [imagePickerVc setDidFinishPickingPhotosHandle:^(NSArray<UIImage *> *photos, NSArray *assets, BOOL isSelectOriginalPhoto) { @strongify(self); [self.wkWebWeipaiJSBridgeModel chooseImageResponseWithPhotos:photos assets:assets]; }]; [self presentViewController:imagePickerVc animated:YES completion:nil];
注意:1.在使用时,如果你不获取原图的话,TZImagePickerController的代理方法或block回调里返回的图片是质量非常差的图片,压缩程度非常高。因为它是直接把PHImageResultIsDegradedKey里的图片返回,从字面上我们就可以看出,这是苹果返回的一个退化的图片,我在前面也说了,这只是返回的一个预览图像,并且TZImagePickerController在创建PHImageRequestOptions的时候,resizeMode使用的是PHImageRequestOptionsResizeModeFast;如果把resizeMode修改成PHImageRequestOptionsResizeModeNone,可以适当的提高。
2.在外部设置photoPreviewMaxWidth超出500~800无效,因为TZImagePickerController在内部设置了范围。
- (void)setPhotoPreviewMaxWidth:(CGFloat)photoPreviewMaxWidth { _photoPreviewMaxWidth = photoPreviewMaxWidth; if (photoPreviewMaxWidth > 800) { _photoPreviewMaxWidth = 800; } else if (photoPreviewMaxWidth < 500) { _photoPreviewMaxWidth = 500; } [TZImageManager manager].photoPreviewMaxWidth = _photoPreviewMaxWidth; }
因为获取到图片质量太差,所以直接使用photos不科学呀!那么由于么个assets都对应照片库中的一个图片,assets中最核心的是localIdentifier就是图片唯一标识,可以通过assets直接获取到系统中照片库的图片资源。
我们应用的图片上传是分两步实现了。第一步:是服务器的js通过客户端注册函数下发图片选择请求,用户选择图片后通过回调机制发送选在的本地图片地址拼装消息数组数据给服务器,注意这一步只是向服务器发送加工过的图片本地地址到服务器,并没有上传图片二进制流。
第二步:1.JS页面通过客户端注册函数下发单个加工过的本地图片地址及上传消息类型,客户端根据这个自定义URL schemes的本地图片地址获取本地图片数据并上传数据到阿里云,上传成功js继续下发相同的消息,客户端按照这个逻辑继续上传图片到服务器。
2.JS页面下上传图片消息的同时通过标签函数调用下载图片数据,客户端通过注册自定义URL schemes拦截该自定义的URL schemes图片下载请求并且返回图片数据,达到在图片上传图片过程中就在js页面显示图片的目的。注意:图片的URL schemes不能使用系统自带的https,不然会出现,post请求body参数被清空,js页面无法通过post请求传递参数的问题。具体参照:《【腾讯Bugly干货分享】WKWebView 那些坑》。
所以我们的应用在上传图片时只有一串字符串,没有图片对象。
无论你是选择了图片后立即上传,还是像我们一样分步上传,标签显示图片。你想得到原始图片,那么你就需要通过localIdentifier或assets获取本地图片上传服务器。
至于第一种情况,你获取到图片assets数组,直接上传图片到服务器,一般你会遇到图片逆时针旋转90度的问题,到底谁动了我的奶酪把我放倒了呢!原因很显然是setDidFinishPickingPhotosHandle返回的assets参数有问题,具体如何我也没有深究,作为程序猿,只要解决问题就行,可以不深究到底哪个参数错误了。这个是原始的代码:
/** 通过资源获取图片的数据 @param mAsset 资源文件 @param imageBlock 图片数据回传 */ - (void)fetchImageWithAsset:(PHAsset*)mAsset imageBlock:(void(^)(NSData* imageData))imageBlock { @weakify(self); NSLog(@"mAsset:%@, mAsset.className:%@", mAsset, mAsset.className); [[PHImageManager defaultManager] requestImageDataForAsset:mAsset options:nil resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) { @strongify(self); // 直接得到最终的 NSData 数据 if (imageBlock) { imageBlock(imageData); // UIImage* image = [UIImage imageWithData:imageData]; // NSString *filePath = [self getChooseImageDirWithFileName:@"test"]; // [self storageImageWithFilePath:filePath image:image]; } }]; }
网上给的解决方案是,判断图片是否旋转了,若选择了就把它旋转过来。代码如下:
/** 通过资源获取图片的数据 @param mAsset 资源文件 @param imageBlock 图片数据回传 */ - (void)fetchImageWithAsset:(PHAsset*)mAsset imageBlock:(void(^)(NSData* imageData))imageBlock { @weakify(self); NSLog(@"mAsset:%@, mAsset.className:%@", mAsset, mAsset.className); [[PHImageManager defaultManager] requestImageDataForAsset:mAsset options:nil resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) { @strongify(self); if (orientation != UIImageOrientationUp) { UIImage* image = [UIImage imageWithData:imageData]; // 尽然弯了,那就板正一下 image = [self fixOrientation:image]; // 新的 数据信息 (不准确的) imageData = UIImageJPEGRepresentation(image, 1.0); // NSString *filePath = [self getChooseImageDirWithFileName:@"test"]; // [self storageImageWithFilePath:filePath image:image]; } // 直接得到最终的 NSData 数据 if (imageBlock) { imageBlock(imageData); // UIImage* image = [UIImage imageWithData:imageData]; // NSString *filePath = [self getChooseImageDirWithFileName:@"test"]; // [self storageImageWithFilePath:filePath image:image]; } }]; } - (UIImage *)fixOrientation:(UIImage *)aImage { // No-op if the orientation is already correct if (aImage.imageOrientation == UIImageOrientationUp) return aImage; // We need to calculate the proper transformation to make the image upright. // We do it in 2 steps: Rotate if Left/Right/Down, and then flip if Mirrored. CGAffineTransform transform = CGAffineTransformIdentity; switch (aImage.imageOrientation) { case UIImageOrientationDown: case UIImageOrientationDownMirrored: transform = CGAffineTransformTranslate(transform, aImage.size.width, aImage.size.height); transform = CGAffineTransformRotate(transform, M_PI); break; case UIImageOrientationLeft: case UIImageOrientationLeftMirrored: transform = CGAffineTransformTranslate(transform, aImage.size.width, 0); transform = CGAffineTransformRotate(transform, M_PI_2); break; case UIImageOrientationRight: case UIImageOrientationRightMirrored: transform = CGAffineTransformTranslate(transform, 0, aImage.size.height); transform = CGAffineTransformRotate(transform, -M_PI_2); break; default: break; } switch (aImage.imageOrientation) { case UIImageOrientationUpMirrored: case UIImageOrientationDownMirrored: transform = CGAffineTransformTranslate(transform, aImage.size.width, 0); transform = CGAffineTransformScale(transform, -1, 1); break; case UIImageOrientationLeftMirrored: case UIImageOrientationRightMirrored: transform = CGAffineTransformTranslate(transform, aImage.size.height, 0); transform = CGAffineTransformScale(transform, -1, 1); break; default: break; } // Now we draw the underlying CGImage into a new context, applying the transform // calculated above. CGContextRef ctx = CGBitmapContextCreate(NULL, aImage.size.width, aImage.size.height, CGImageGetBitsPerComponent(aImage.CGImage), 0, CGImageGetColorSpace(aImage.CGImage), CGImageGetBitmapInfo(aImage.CGImage)); CGContextConcatCTM(ctx, transform); switch (aImage.imageOrientation) { case UIImageOrientationLeft: case UIImageOrientationLeftMirrored: case UIImageOrientationRight: case UIImageOrientationRightMirrored: // Grr... CGContextDrawImage(ctx, CGRectMake(0,0,aImage.size.height,aImage.size.width), aImage.CGImage); break; default: CGContextDrawImage(ctx, CGRectMake(0,0,aImage.size.width,aImage.size.height), aImage.CGImage); break; } // And now we just create a new UIImage from the drawing context CGImageRef cgimg = CGBitmapContextCreateImage(ctx); UIImage *img = [UIImage imageWithCGImage:cgimg]; CGContextRelease(ctx); CGImageRelease(cgimg); return img; }
买嘎,看似把问题解决了,你发现你图片格式不正确了。若你的图片是透明背景的png文件,经过它一处理变成黑背景图片了(png格式的图片支持透明,jpg格式的图片不支持透明背景)。由于图片不同,压缩率不同,他们的图片大小也不同,哪怕你从是手机拍照的图片,经过imageData = UIImageJPEGRepresentation(image, 1.0);转换后也会变大20%以上,见文站《图像的压缩算法–尺寸压缩、格式压缩和品质压缩》,至于相机拍照的照片通过UIImageJPEGRepresentation转换,这个压缩比多少才相当,这个和图片色彩丰富度,尺寸,大小,分辨率等参数相关,就是你把它设置为平均值0.7(大姐,你多测试几组你发现这个平均参数可不是0.5,而是更接近0.7,不能用一两组数据决定全部),你还是不能解决格式不同的问题。至于你说我的图片是png格式是png格式,使用imageData = UIImagePNGRepresentation(image);不就解决透明背景了吗?同时你将会遇到一个无解的问题,那就是UIImagePNGRepresentation没有压缩质量参数,通常你得到的图片会变大20%以上,特别是通过ps压缩的高压缩率超大尺寸图片,一个几兆的图片经过它这么一折腾就变成几百兆就很正常。你的手机玩一个300兆的png图片(我实际真的这样转出来一个300多兆的图片,详细见文章《超大尺寸的图片无法使用UIActivityViewController分享问题》)得了吗?这样通过旋转图像的方式来纠正图片被撂倒的方式本来就不完美。
回到正题,既然是怀疑PHAsset参数设置不正确导致获取到的图片被撂倒,那么从这个怀疑出发,UIImageOrientationUp是默认枚举类型初始值0,并且PHAsset最核心的是localIdentifier这个图片唯一标识。那么我们是否可以通过新建立PHAsset来实现获取到的图片保证正经模式而不神经病模式呢!那么来点正点的:
NSString *localIdentifier = phAsset.localIdentifier; NSMutableArray *tempArr = [NSMutableArray array]; [tempArr addSafeObject: localIdentifier]; PHFetchResult *fetchResult = [PHAsset fetchAssetsWithLocalIdentifiers:tempArr options:nil]; @weakify(self [self fetchImageWithAsset:fetchResult.firstObject imageBlock:^(NSData *imageData) { @strongify(self); NSLog(@"imageData:%@", imageData); if (!imageData) { if (block) { block(nil); } return ; } //图片上传阿里云服务器略去 }];
上传阿里云服务器成功后再下载下来,发现图片和原始图片的大小和方向都一样。一切问题搞定了,要是谁想研究可以继续通过尝试修改原来的PHAsset各个参赛看是哪个参数把获取的图片撂倒了,不过我不敢保证它的参数是否有只读参数,PHAsset的参数并非表面上看到的那么少,如它携带有文件名,不仔细找可能就忽略了。
注意:上传服务器多张图片时,虽然https支持post请求传递多张图片,但是有可能九张图片并发发送图片,由于服务器处理九个及以上这样大数据流的文件比较费时间,可能超过服务器的超时时间导致部分上传图片成功,部分上传失败的情况。当然也可能网络不好上传失败。一般服务器都对上传的图片进行压缩处理,那更费时间了,也可能对大尺寸高压缩的图片压缩不当生成一个几百兆的图片,导致处理失败。所以这样的图片若比较多可以一次上传一张图片,待上传成功再上传另一张直到上传完图片,这样能减少失败的概率,但是造成上传时间增加,所以要根据图片的多少和上传失败的概率决定使用那种方案。
至于对我们的应用分两步上传图片,我将写一篇关于js标签函数在客户端使用的文章。不过当时我在使用标签函数时也遇到这样问题。