iOS模仿系統相機拍照你不曾注意過的細節

weixin_33860722發表於2018-07-01

距離上次寫部落格竟然過了一個月了,一方面是最近專案比較忙,另一方面是實在是有點兒懈怠了,強烈譴責一下自己。其實我最近在看一些技術書籍,發現一些好的書真心對自己幫助很大,看書的過程,好多原來模糊的概念、問題,都能感覺恍然大悟。當提筆想總結成一篇文章的時候,發現網上早已經有大量的優秀文章出現,所以就不敢獻醜了。今天寫的一篇文章,是最近自己專案中用到的,不算什麼難點,只是感覺有必要記錄一下。

需求

由於我們APP整合了有道翻譯的SDK,需要將拍出來的圖片翻譯成對應的語言,但是有道的SDK目前還做的不是很完善(比如:照片傾斜的時候,返回的角度不是很對,有道的技術說下個版本可能會更新)。於是產品要求拍照頁面做成跟系統相機類似,當使用者橫屏拍攝的時候,需要客戶端自己將圖片糾正回來,倒著拍的時候亦然。

自定義相機功能就不多說了,網上有大量的優秀文章,這裡隨便從網上找了一個,需要的可以參考下

基礎知識

首先我們需要知道每一個UIImage物件,都有一個imageOrientation屬性,裡面儲存著方向資訊:

typedef NS_ENUM(NSInteger, UIImageOrientation) {
    UIImageOrientationUp,            // default orientation
    UIImageOrientationDown,          // 180 deg rotation
    UIImageOrientationLeft,          // 90 deg CCW
    UIImageOrientationRight,         // 90 deg CW
    UIImageOrientationUpMirrored,    // as above but image mirrored along other axis. horizontal flip
    UIImageOrientationDownMirrored,  // horizontal flip
    UIImageOrientationLeftMirrored,  // vertical flip
    UIImageOrientationRightMirrored, // vertical flip
};

根據這個屬性資訊,我們便可以對影像進行相應的旋轉,將圖片轉到正確的方向,如何旋轉??有兩種解決方案:

第一種:給UIImage新增Category

- (UIImage *)fixOrientation {

    // No-op if the orientation is already correct
    if (self.imageOrientation == UIImageOrientationUp) return self;

    // 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 (self.imageOrientation) {
        case UIImageOrientationDown:
        case UIImageOrientationDownMirrored:
            transform = CGAffineTransformTranslate(transform, self.size.width, self.size.height);
            transform = CGAffineTransformRotate(transform, M_PI);
            break;

        case UIImageOrientationLeft:
        case UIImageOrientationLeftMirrored:
            transform = CGAffineTransformTranslate(transform, self.size.width, 0);
            transform = CGAffineTransformRotate(transform, M_PI_2);
            break;

        case UIImageOrientationRight:
        case UIImageOrientationRightMirrored:
            transform = CGAffineTransformTranslate(transform, 0, self.size.height);
            transform = CGAffineTransformRotate(transform, -M_PI_2);
            break;
        case UIImageOrientationUp:
        case UIImageOrientationUpMirrored:
            break;
    }

    switch (self.imageOrientation) {
        case UIImageOrientationUpMirrored:
        case UIImageOrientationDownMirrored:
            transform = CGAffineTransformTranslate(transform, self.size.width, 0);
            transform = CGAffineTransformScale(transform, -1, 1);
            break;

        case UIImageOrientationLeftMirrored:
        case UIImageOrientationRightMirrored:
            transform = CGAffineTransformTranslate(transform, self.size.height, 0);
            transform = CGAffineTransformScale(transform, -1, 1);
            break;
        case UIImageOrientationUp:
        case UIImageOrientationDown:
        case UIImageOrientationLeft:
        case UIImageOrientationRight:
            break;
    }

    // Now we draw the underlying CGImage into a new context, applying the transform
    // calculated above.
    CGContextRef ctx = CGBitmapContextCreate(NULL, self.size.width, self.size.height,
                                             CGImageGetBitsPerComponent(self.CGImage), 0,
                                             CGImageGetColorSpace(self.CGImage),
                                             CGImageGetBitmapInfo(self.CGImage));
    CGContextConcatCTM(ctx, transform);
    switch (self.imageOrientation) {
        case UIImageOrientationLeft:
        case UIImageOrientationLeftMirrored:
        case UIImageOrientationRight:
        case UIImageOrientationRightMirrored:
            // Grr...
            CGContextDrawImage(ctx, CGRectMake(0,0,self.size.height,self.size.width), self.CGImage);
            break;

        default:
            CGContextDrawImage(ctx, CGRectMake(0,0,self.size.width,self.size.height), self.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;
}

第二種:利用drawInRect方法將影像畫到畫布上

- (UIImage *)normalizedImage {
    if (self.imageOrientation == UIImageOrientationUp) return self; 

    UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
    [self drawInRect:(CGRect){0, 0, self.size}];
    UIImage *normalizedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return normalizedImage;
}

通過上面兩種方式轉換之後的UIImage物件,其imageOrientation屬性,都會被修改成UIImageOrientationUp,這樣將圖片傳到後臺,或者匯出相簿的時候,就不會出現照片旋轉90度的問題。

但是有時候我們希望圖片該旋轉的時候,按照我們的意願旋轉(比如橫評拍攝的時候),豎直拍攝的時候,影像正常顯示,這時候我們就不能直接用上面的方法來判斷了。仔細觀察系統相機的拍攝,我發現除了豎直拍攝以外,別的情況下拍攝,圖片都會自動旋轉,這個時候就需要我們利用iPhone手機自帶的硬體感測器對方向進行判斷,以達到我們想要的結果,這裡主要用到加速儀

加速儀(型別:CMAcceleration)

加速儀可以檢測三維空間中的加速度 ,座標對應如下:

423503-9829f6e75019e644.png
o_coremotion1

例如:當垂直手持手機且頂部向上,Y座標上回收到 -1G的加速度,(0,-1,0),當手機頭部朝下,得到的各個座標為:(0,1,0)

主要程式碼如下:

- (void)startDeviceMotion{
    if (![self.motionManager isDeviceMotionAvailable]) {return;}

    [self.motionManager setDeviceMotionUpdateInterval:1.f];
    [self.motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMDeviceMotion * _Nullable motion, NSError * _Nullable error) {
        
        double gravityX = motion.gravity.x;
        double gravityY = motion.gravity.y;
        
        if (fabs(gravityY)>=fabs(gravityX)) {
            
            if (gravityY >= 0) {
                
                // UIDeviceOrientationPortraitUpsideDown
                [self setDeviceDirection:SSDeviceDirectionDown];
                NSLog(@"頭向下");
                
            } else {
                
                // UIDeviceOrientationPortrait
                [self setDeviceDirection:SSDeviceDirectionUp];
                NSLog(@"豎屏");
            }
            
        } else {
            
            if (gravityX >= 0) {
                // UIDeviceOrientationLandscapeRight
                [self setDeviceDirection:SSDeviceDirectionRight];
                NSLog(@"頭向右");
            } else {
                
                // UIDeviceOrientationLandscapeLef
                [self setDeviceDirection:SSDeviceDirectionLeft];
                NSLog(@"頭向左");
            }
        }
    }];
}

獲取到方向資訊,下面就可以對圖片進行對應的處理了,主要用到了下面的這個方法:

- (instancetype)initWithCIImage:(CIImage *)ciImage scale:(CGFloat)scale orientation:(UIImageOrientation)orientation NS_AVAILABLE_IOS(6_0);

該方法的作用是:

Creates and returns an image object with the specified scale and orientation factors.
建立並返回具有指定比例和方向特徵的image物件。

最後對拍攝的圖片進行處理:

UIImage *transImage = [rsltImage fixOrientation];
        
        switch (self.deviceDirection) {
            case SSDeviceDirectionUp:
                transImage = [rsltImage fixOrientation];
                break;
            case SSDeviceDirectionLeft:
                transImage = [rsltImage fixImageByOrientation:UIImageOrientationLeft];
                break;
            case SSDeviceDirectionRight:
                transImage = [rsltImage fixImageByOrientation:UIImageOrientationRight];
                break;
            case SSDeviceDirectionDown:
                transImage = [rsltImage fixImageByOrientation:UIImageOrientationDown];
                break;
            default:
                break;
        }

最終效果圖

423503-20d63418c38280fd.gif
take_pic_ocr

總結

功能實現起來其實並不難,當時和同事糾結的地方在於,到底是採用支援橫豎屏還是採用加速度感測器上面,最後經過分析系統相機,我還是採用了利用感測器做判斷,期間也是查閱了很多的技術文章,無意中發現了一篇真心值得仔細閱讀的關於圖片解壓縮的文章。最後,再次對最近的鬆懈進行反思,繼續擼起袖子,加油幹!!!

Refrence

https://www.cnblogs.com/sunyanyan/p/5213854.html

http://feihu.me/blog/2015/how-to-handle-image-orientation-on-iOS/

相關文章