关注“之家技术”,获取更多技术干货
总篇第144篇 2022年第19篇
场景
原因探查
经过调研发现,iOS相机拍照时的预览内容与实际得到的真实图片不是100%一致的。当我们在相机拍摄时,自定义了输出照片的长宽像素值(比如1280×720),iPhone的取景框默认会撑满不留黑边的展示出照片,会进行"等比放大"处理,这样就会有部分图像是超出屏幕可视区的。这就好比单反相机的一个参数:查看区域和拍摄区域的视野率。
我们这里的VIN相机默认设置了1280×720的分辨率进行拍摄,经过测试,iPhone拍摄后获取到的真实图片与图像预览区视图存在一定的换算关系,如下图:
手机纵向拍摄举例,这里分为2种情况:
1.情况A:当实际照片的宽高比大于屏幕拍摄预览的宽高比时,系统会将实际照片和拍摄预览区以高度对齐,然后在水平方向上居中展示,左右部分超出。
a.手机屏幕看到的图像部分为半透明蓝色区域;
b.图片左右侧半透明绿色区域示意为,实际照片超出屏幕的部分;
c.淡粉色透明区域为实际VIN码取景框与屏幕边界的间距;
2.情况B:当实际图片的宽高比小于拍摄区的宽高比时,系统会将实际图片和拍摄区以宽度对齐,然后在垂直方向上居中展示,上下部分超出。
a.手机屏幕看到的为半透明粉色区域;
b.图片上下半透明绿色区域为实际照片超出屏幕的部分;
c.VIN码取景框左右淡粉色透明区域为取景框屏幕边界的间距;
由此可以看出,当我们需要在拍照获取到的照片上,准确计算出取景框看到的图像部分进行裁切时,根据情况A或B,在计算了屏幕边界距离的基础上,需要再加上屏幕上的非可见区域部分的距离,才可以得到一个完美的所见即所得的VIN照片。
代码处理
如上图所示,由于不同手机屏幕宽高比的不同,我们的预览区的宽高比也是动态的。所以针对出现的图片宽高比和预览区宽高比不同的情况,我们要做出相应的位置补偿。
根据图片和预览区的宽高比,分别计算在A、B两种情况下水平和垂直方向上的补偿,然后重新计算裁切区在屏幕大小照片上的大小和位置信息。
- (CGRect)fixCropRect:(CGRect)cropRect realImageSize:(CGSize)realImageSize previewRect:(CGRect)previewRect {
CGRect fixedCropRect = cropRect;
// 图片宽高比
CGFloat imageRatio = realImageSize.width / realImageSize.height;
// 预览框的宽高比
CGFloat previewRatio = previewRect.size.width / previewRect.size.height;
if (imageRatio > previewRatio) { // 图片的宽高比 > 拍摄区的宽高比
/*
当出现情况A的时候:
图片宽高比: 720/1280 = 0.5625
拍摄预览区宽高比: 390/753 = 0.517928287
图片的宽高比 > 拍摄区的宽高比时, 系统自动以【高】对齐, 实际图片比可视区宽, 横向居中【左右】超出屏幕
*/
// 计算实际图片换算到屏幕的宽度: 423.5625
CGFloat imageScreenWidth = imageRatio * previewRect.size.height;
// 计算出 x 需要补偿的距离, 并且补偿 x
fixedCropRect.origin.x = (imageScreenWidth - cropRect.size.width)/2;
} else if (imageRatio < previewRatio) { // 图片的宽高比 < 拍摄区的宽高比
/*
当出现情况B的时候:
图片宽高比: 720/1280 = 0.5625
拍摄预览区宽高比: 375/603 = 0.621890547
图片的宽高比 < 拍摄区的宽高比时, 系统自动以【宽】对齐, 实际图片比可视区高, 纵向居中【上下】超出屏幕
*/
// 计算实际图片换算到屏幕的高度: 667
CGFloat imageScreenHeight = previewRect.size.width/imageRatio;
// 计算出 y 需要补偿的距离
CGFloat diff_y = ( imageScreenHeight - previewRect.size.height )/2;
// 对 y 进行补偿
fixedCropRect.origin.y += diff_y;
} else {
// 图片的宽高比 == 拍摄区的宽高比, 1:1 的情况, 不用做裁切框定位补偿
}
return fixedCropRect;
}
根据图片和预览区的宽高比,分别计算在A、B两种情况下水平和垂直方向上的缩放补偿,然后重新计算裁切区在实际图片上的大小和位置信息。
// 计算裁切区域,算换的到之际图片大小后的位置
- (CGRect)subimageCropRectOnImage:(UIImage *)orgimage cropRect:(CGRect)cropRect previewRect:(CGRect)previewRect {
CGSize imageSize = orgimage.size;
CGFloat PREVIEW_WIDTH = previewRect.size.width;
CGFloat PREVIEW_HEIGHT = previewRect.size.height;
CGRect rectOnImage;
CGFloat imageRatio = imageSize.width / imageSize.height;
CGFloat previewRatio = PREVIEW_WIDTH / PREVIEW_HEIGHT;
if (imageRatio > previewRatio) { // 图片的宽高比 > 拍摄区的宽高比
/*
当出现情况A的时候:
图片宽高比: 720/1280 = 0.5625
拍摄预览区宽高比: 390/753 = 0.517928287
图片的宽高比 > 拍摄区的宽高比时, 系统自动以【高】对齐, 实际图片比可视区宽, 横向居中【左右】超出屏幕
*/
// 以高为基准, 计算图片放大比例
CGFloat scale = PREVIEW_HEIGHT / imageSize.height;
CGFloat x = cropRect.origin.x / scale;
CGFloat y = cropRect.origin.y / scale;
CGFloat w = cropRect.size.width / scale;
CGFloat h = cropRect.size.height / scale;
rectOnImage = CGRectMake(x, y, w, h);
} else { // 图片的宽高比 <= 拍摄区的宽高比
/*
当出现情况B的时候:
图片宽高比: 720/1280 = 0.5625
拍摄预览区宽高比: 375/603 = 0.621890547
图片的宽高比 < 拍摄区的宽高比时, 系统自动以【宽】对齐, 实际图片比可视区高, 纵向居中【上下】超出屏幕
*/
// 以宽为基准, 计算图片放大比例
CGFloat scale = PREVIEW_WIDTH / imageSize.width;
CGFloat x = cropRect.origin.x / scale;
CGFloat y = cropRect.origin.y / scale;
CGFloat w = cropRect.size.width / scale;
CGFloat h = cropRect.size.height / scale;
rectOnImage = CGRectMake(x, y, w, h);
}
return rectOnImage;
}
整合2部分代码,一步输出正确裁切框在图片上的位置,获得正确的裁切照片。
- (CGRect)subimageCropRectOnImage:(UIImage *)orgimage cropRect:(CGRect)cropRect previewRect:(CGRect)previewRect {
CGSize imageSize = orgimage.size;
CGFloat PREVIEW_WIDTH = previewRect.size.width;
CGFloat PREVIEW_HEIGHT = previewRect.size.height;
CGRect rectOnImage;
CGFloat imageRatio = imageSize.width / imageSize.height;
CGFloat previewRatio = PREVIEW_WIDTH / PREVIEW_HEIGHT;
if (imageRatio > previewRatio) { // 图片的宽高比 > 拍摄区的宽高比
/*
当出现情况A的时候:
图片宽高比: 720/1280 = 0.5625
拍摄预览区宽高比: 390/753 = 0.517928287
图片的宽高比 > 拍摄区的宽高比时, 系统自动以【高】对齐, 实际图片比可视区宽, 横向居中【左右】超出屏幕
*/
// ↓↓↓ 补偿计算 ↓↓↓
// 求实际图片换算到屏幕的宽度: 423.5625
CGFloat imageScreenWidth = imageRatio * previewRect.size.height;
// 计算出 x 需要补偿的距离, 并且补偿 x
cropRect.origin.x = (imageScreenWidth - cropRect.size.width)/2;
// ↓↓↓ 比例放大计算 ↓↓↓
// 以高为基准, 计算图片放大比例
CGFloat scale = PREVIEW_HEIGHT / imageSize.height;
CGFloat x = cropRect.origin.x / scale;
CGFloat y = cropRect.origin.y / scale;
CGFloat w = cropRect.size.width / scale;
CGFloat h = cropRect.size.height / scale;
rectOnImage = CGRectMake(x, y, w, h);
} else { // 图片的宽高比 <= 拍摄区的宽高比
/*
当出现情况B的时候:
图片宽高比: 720/1280 = 0.5625
拍摄预览区宽高比: 375/603 = 0.621890547
图片的宽高比 < 拍摄区的宽高比时, 系统自动以【宽】对齐, 实际图片比可视区高, 纵向居中【上下】超出屏幕
*/
// ↓↓↓ 补偿计算 ↓↓↓
// 计算实际图片换算到屏幕的高度: 667
CGFloat imageScreenHeight = previewRect.size.width/imageRatio;
// 计算出 y 需要补偿的距离
CGFloat diff_y = ( imageScreenHeight - previewRect.size.height )/2;
// 对 y 进行补偿
cropRect.origin.y += diff_y;
// ↓↓↓ 比例放大计算 ↓↓↓
// 以宽为基准, 计算图片放大比例
CGFloat scale = PREVIEW_WIDTH / imageSize.width;
CGFloat x = cropRect.origin.x / scale;
CGFloat y = cropRect.origin.y / scale;
CGFloat w = cropRect.size.width / scale;
CGFloat h = cropRect.size.height / scale;
rectOnImage = CGRectMake(x, y, w, h);
}
return rectOnImage;
}
// 输出最终图片
- (UIImage *)ak_subImageOfImage:(UIImage *)orgimage cropRectOnImage:(CGRect)rectOnImage {
UIImage *submage = nil;
CGImageRef imageRotatedRef = orgimage.CGImage;
CGImageRef subImageRef = CGImageCreateWithImageInRect(imageRotatedRef, rectOnImage);
UIGraphicsBeginImageContext(rectOnImage.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage(context, rectOnImage, subImageRef);
submage = [UIImage imageWithCGImage:subImageRef];
UIGraphicsEndImageContext();
CGImageRelease(subImageRef);
return submage;
}
作者简介
二手车事业部-技术部
孙洪琳
二手车事业部-技术部
李亚
加入汽车之家多年,一直从事研发工作,现负责二手车之家以及其他汽车之家二手车业务的相关研发工作。
阅读更多:
▼ 关注「之家技术」,获取更多技术干货 ▼