3.4 扩展视图控制器的实现
让我们打开ViewController.mm,完成该类的实现.首先,让我们为blendMode属性增加自定义的getter和setter方法.getter方法简单地返回了_blendMode实例变量,如下图所示:
- (BlendMode)blendMode {
return _blendMode;
}
setter会检查新值是否和旧值相同.如果相同,新值将会分配给_blendMode,并且blendSettingsChanged属性将会被设置为YES,如下所示:
- (void)setBlendMode:(BlendMode)blendMode {
if (blendMode != _blendMode) {
_blendMode = blendMode;
self.blendSettingsChanged = YES;
}
}
现在,让我们着眼于processImageHelper:
方法的实现.代码很长,所以我们考虑将他划分为4个功能块.首先,如果用户没有选择要混合的前景图像,这个函数应该直接退出,如下代码所示:
- (void)processImageHelper:(cv::Mat &)mat {
if (originalBlendSrcMat.empty()) {
// No blending source has been selected.
// Do nothing.
return;
}
}
接下来,我们需要确保前景图片已经经过了合适的处理.他必须转换为和背景图片相同的尺寸和颜色格式.此外,我们可以单独预先计算好取决于前景图像的混合算法的任何一部分.这样,当背景图片改变的时候,可以减少每一帧的计算量.下面的条件判断检查线的处理是否有必要进行新的的处理:
if (convertedBlendSrcMat.rows != mat.rows ||
convertedBlendSrcMat.cols != mat.cols ||
convertedBlendSrcMat.type() != mat.type() ||
self.blendSettingsChanged) {
如果有必要进行新的处理,我们首先调用一个帮助方法来调整前景图片的尺寸并转换颜色格式.然后,我们应用混合算法中前景指定的部分,这个算法取决于用户选择的blendingmode.在处理的结尾,我们将blendSettingsChanged
属性设为NO,因为我们已经处理对应的变化.下面是相关的代码:
// Resize the blending source and convert its format.
[self convertBlendSrcMatToWidth:mat.cols height:mat.rows];
// Apply any mode-dependent operations to the blending source.
switch (self.blendMode) {
case Screen: {
/* Pseudocode:
convertedBlendSrcMat = 255 – convertedBlendSrcMat;
*/
cv::subtract(255.0, convertedBlendSrcMat, convertedBlendSrcMat);
break;
}
case HUD: {
/* Pseudocode:
convertedBlendSrcMat = 255 – Laplacian(GaussianBlur(convertedBlendSrcMat));
*/
cv::GaussianBlur(convertedBlendSrcMat, convertedBlendSrcMat, cv::Size(5, 5), 0.0); cv::Laplacian(convertedBlendSrcMat, convertedBlendSrcMat, -1, 3);
if (!self.videoCamera.grayscaleMode) {
// The background is in color.
// Give the foreground a yellowish green tint, which
// will stand out against most backgrounds.
cv::multiply(cv::Scalar(0.0, 1.0, 0.5),convertedBlendSrcMat, convertedBlendSrcMat);
}
cv::subtract(255.0, convertedBlendSrcMat, convertedBlendSrcMat);
break;
}
default:
break;
}
self.blendSettingsChanged = NO;
}
为了完成processImageHelper:
方法,我们将处理过的前景图像和最新的背景图像进行混合.此外,混合的算法是根据用户选择的混合模式决定的.下面是相关的代码:
// Combine the blending source and the current frame.
switch (self.blendMode) {
case Average:
/* Pseudocode:
mat = 0.5 * mat + 0.5 * convertedBlendSrcMat; */
cv::addWeighted(mat, 0.5, convertedBlendSrcMat, 0.5, 0.0, mat);
break;
case Multiply:
/* Pseudocode:
mat = mat * convertedBlendSrcMat / 255; */
cv::multiply(mat, convertedBlendSrcMat, mat, 1.0 / 255.0);
break;
case Screen:
case HUD:
/* Pseudocode:
mat = 255 – (255 – mat) * convertedBlendSrcMat / 255; */
cv::subtract(255.0, mat, mat);
cv::multiply(mat, convertedBlendSrcMat, mat, 1.0 / 255.0);
cv::subtract(255.0, mat, mat);
break;
default:
break;
}
}
[对于混合算法的解释,请参考本章开头’关于混合图像的思考’这一节]
当用户按下了’Blend Src’按钮,我们首先检查相册是否可用.如果不可用,意味着用户拒绝了LightWork访问Photos的权限,我们将显示一个错误提示框然后返回.否则,我们将创建一个标准的图片选择器,该选择器是UIImagePickerController类的实例.在某种程度上,选择器是可配置的。我们将指定我们的视图控制器是选择器的代理,所以我们的视图控制器需要实现回调。我们还将告诉选取器我们要从照片相册中选取一个静态图像(而不是视频)。最后,我们将显示选择器。下面是’Blend Src’按钮的回调:
- (IBAction)onBlendSrcButtonPressed {
if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeSavedPhotosAlbum]) {
// The Photos album is unavailable.
// Show an error message.
UIAlertController *alert = [UIAlertController
alertControllerWithTitle:@"Photos album unavailable"
message:@"Go to the Settings app and give LightWork permission to access your Photos album."
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
[alert addAction:okAction];
[self presentViewController:alert animated:YES completion:nil]; return;
}
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.delegate = self;
// Pick from the Photos album.
picker.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
// Pick from still images, not movies.
picker.mediaTypes = [NSArray arrayWithObject:@"public.image"];
[self presentViewController:picker animated:YES completion:nil];
}
作为图片选择器的代理,我们的ViewController需要负责处理在选择器中的交互.甚至需要我们告诉选择器何时消失.UIImagePickerControllerDelegate协议定义了一个处理图片的回调.这个回调接收该图片选择器和一个叫做info的字典作为参数,该字典包含了用户选择的详细信息.当用户选择了图片,我们就应该让选择器消失.然后,我们会从info字典中获取到图片的信息,并且将他转换成cv::Mat类型.如果没有选择混合模式,默认使用’Average’混合模式.最后,我们设置’blendSettingsChanged’属性为YES.下面是回调的实现:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info {
[picker dismissViewControllerAnimated:YES completion:nil];
UIImage *image = [info objectForKey:@"UIImagePickerControllerOriginalImage"];
UIImageToMat(image, originalBlendSrcMat);
if (self.blendMode == None) {
// Blending is currently deactivated.
// Activate "Average" blending so that the user sees some
// result.
self.blendMode = Average;
}
self.blendSettingsChanged = YES;
}
UIImagePickerControllerDelegate协议中还为选择器的取消按钮定义了回调.当这个按钮被按下,我们需要简单滴关闭这个选择器,代码如下:
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
[picker dismissViewControllerAnimated:YES completion:nil];
}
当用户按下了’BlendMode’按钮,我们将显示一个弹出菜单来选择混合模式.弹出菜单其实就是一个配置从工具栏中的按钮弹出的警告框.我们用一个帮助方法来创建每一个按钮.下面是’Blend Mode’按钮的回调的实现:
- (IBAction)onBlendModeButtonPressed:(UIBarButtonItem *)sender {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
alert.popoverPresentationController.barButtonItem = sender;
UIAlertAction *averageAction = [self blendModeActionWithTitle:@"Average" blendMode:Average]; [alert addAction:averageAction];
UIAlertAction *multiplyAction = [self blendModeActionWithTitle:@"Multiply" blendMode:Multiply]; [alert addAction:multiplyAction];
UIAlertAction *screenAction = [self blendModeActionWithTitle:@"Screen" blendMode:Screen]; [alert addAction:screenAction];
UIAlertAction *hudAction = [self blendModeActionWithTitle:@"HUD" blendMode:HUD]; [alert addAction:hudAction];
UIAlertAction *noneAction = [self blendModeActionWithTitle:@"None" blendMode:None]; [alert addAction:noneAction];
[self presentViewController:alert animated:YES completion:nil];
}
当用户按下了弹出菜单的按钮,blendMode属性将被设为对应的值.如果,当前背景是静态图像,我们将刷新他.下面是创建菜单按钮和回调的帮助方法:
- (UIAlertAction *)blendModeActionWithTitle:(NSString *)title blendMode:(BlendMode)blendMode {
UIAlertAction *action = [UIAlertAction actionWithTitle:title style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
self.blendMode = blendMode;
if (!self.videoCamera.running) {
[self refresh];
}
}];
return action;
}
最后,让我们着眼于将前景图像转换为匹配背景图的尺寸和颜色格式的帮助方法.首先,我们在前景图中选择一个子区域,这个子区域和背景图的宽高比是匹配的.然后,我们使用一个叫做lanczos的高质量插值算法来调整这个子区域的大小。最后再将已经调整了大小的图片的颜色格式转换为灰度图或者BGRA,就结束了我们的转换.下面是实现:
- (void)convertBlendSrcMatToWidth:(int)dstW height:(int)dstH {
double dstAspectRatio = dstW / (double)dstH;
int srcW = originalBlendSrcMat.cols;
int srcH = originalBlendSrcMat.rows;
double srcAspectRatio = srcW / (double)srcH;
cv::Mat subMat; if (srcAspectRatio < dstAspectRatio) {
int subMatH = (int)(srcW / dstAspectRatio); int startRow = (srcH - subMatH) / 2;
int endRow = startRow + subMatH; subMat = originalBlendSrcMat.rowRange(startRow, endRow);
} else {
int subMatW = (int)(srcH * dstAspectRatio);
int startCol = (srcW - subMatW) / 2;
int endCol = startCol + subMatW; subMat = originalBlendSrcMat.colRange(startCol, endCol);
}
cv::resize(subMat, convertedBlendSrcMat, cv::Size(dstW, dstH), 0.0, 0.0, cv::INTER_LANCZOS4);
int cvtColorCode; if (self.videoCamera.grayscaleMode) {
cvtColorCode = cv::COLOR_RGBA2GRAY;
}
else {
cvtColorCode = cv::COLOR_RGBA2BGRA;
}
cv::cvtColor(convertedBlendSrcMat, convertedBlendSrcMat,cvtColorCode);
}
到现在,新版本的LightWork已经完成了,我们可以构建然后运行他.