1. 程式人生 > >3.4 擴充套件檢視控制器的實現

3.4 擴充套件檢視控制器的實現

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已經完成了,我們可以構建然後執行他.

###返回到第三章目錄###
###返回到書籍目錄###