图片朝向,与滤镜(透视校正)

701 阅读3分钟

前言:

本文承接:

照片选择区域功能的另一实现: 加动效

及其他两篇

接着讲述选择区域相关的处理

旋转,选择区域,对选择的区域做透视校正的滤镜处理

为了简化问题,

项目设置为,仅 iPhone 竖屏

运行在 iPad 上面

这是拍照后,经历了一次左旋的图片

问题

使用滤镜(透视校正)简单,旋转后的图片,很可能与旋转前的图片朝向不一致,这影响了滤镜的效果

场景

ipad 拍照,默认横屏向上,跑 iPhone 竖屏的应用,

相机设置为

connection?.videoOrientation = AVCaptureVideoOrientation.portrait

session.sessionPreset = .hd1280x720

拍出来的照片,朝向 pic.imageOrientation = .right

size 是

  - width : 720.0
  - height : 1280.0
为了简化计算

选择区域 crop view 的 frame = image view 的 frame

crop view 上面的四个角落点的坐标,基于 crop view 的 bounds,

即 crop view 上面的四个角落点的坐标,基于 CGRect( CGPoint.zero, image view 的 frame.size )

需要,旋转后的 image 朝向始终为向上

拿到的图片朝向,向右。滤镜使用的是正常的开发坐标,结果比较搞。

实现

简单的滤镜代码

// 第一个参数,图片视图的 bounds,用 size

// 第二个参数,待处理的图片

// 第三个参数,四个坐标

static public func cropImage(in rect: CGRect,with img: UIImage, quad corners: [CGPoint]) throws -> UIImage {
        let imgSize = img.size
        // 找出图片视图的 bounds 内,图片 aspect ratio 的 frame
        let f = AVMakeRect(aspectRatio: imgSize, insideRect: rect)
        // 做一个放大,当前视图内的坐标,方法到图片对应的坐标
        let quad = corners.map { (pt) -> CGPoint in
            return pt.inner(img: imgSize, relative: f.size)
        }
        let ciImage = CIImage(image: img)
        // print("rect: ", rect, "corners: ",corners)
        let perspectiveCorrection = CIFilter(name: "CIPerspectiveCorrection")
        // 保证四个坐标是,左上,右上,右下,左下
        let orderedQuad = try orderPointsInQuadrangle(quad: quad)
        // 下面是滤镜的标准流程
        let context = CIContext(options: nil)
        // print("ordered quad: ", orderedQuad, "imgSize: ", imgSize)
        
        guard let transform = perspectiveCorrection else {
            throw SECropError.unknown
        }
        transform.setValue(CIVector(cgPoint: orderedQuad[0].cartesian(for: imgSize)),
                           forKey: "inputTopLeft")
        transform.setValue(CIVector(cgPoint: orderedQuad[1].cartesian(for: imgSize)),
                           forKey: "inputTopRight")
        transform.setValue(CIVector(cgPoint: orderedQuad[2].cartesian(for: imgSize)),
                           forKey: "inputBottomRight")
        transform.setValue(CIVector(cgPoint: orderedQuad[3].cartesian(for: imgSize)),
                           forKey: "inputBottomLeft")
        transform.setValue(ciImage, forKey: kCIInputImageKey)
        
        guard let perspectiveCorrectedImg = transform.outputImage, let cgImage = context.createCGImage(perspectiveCorrectedImg, from: perspectiveCorrectedImg.extent) else {
            throw SECropError.unknown
        }
        
        return UIImage(cgImage: cgImage, scale: img.scale, orientation: img.imageOrientation)
    }

滤镜使用错误如下:

除了朝向问题,

  • 还有坐标顺序不对,必须是左上 -> 右上 -> 右下 -> 左下

  • 坐标放大错误,太大了,超出了图片的区域

拍照后,图片处理

func picTaken(img imgData: Data){
        if let pic = UIImage(data: imgData){
            if pic.imageOrientation == .right{
                // 文中 ipad 的情况,先改朝向,再翻转
                takedImage = pic.up.image(rotated: 1)
            }
            else{
                takedImage = pic
            }
        }
        picCommon()
    }

修改朝向



extension UIImage{

    var up: UIImage{
        image(orientation: .up)
    }

    func image(orientation orient: UIImage.Orientation) -> UIImage{
        if let cg = cgImage{
            return UIImage(cgImage: cg, scale: scale, orientation: orient)
        }
        else{
            return self
        }
    }
    

}

旋转代码,也挺简单,

没有做 fix orientation,

具体见 github repo

滤镜前,图片预处理

            let idx = Int(angleX)
            guard let corners = cropView.cornerLocations, let img = takedImage?.image(rotated: idx) else { return }
            // 点击旋转按钮,没有旋转图片,只记录旋转图片的次数, 一次 0.5 个 π
            // 点击旋转按钮, ImageView 做 rotate tranform , 此时不直接影响 bounds
            // 左右旋,需要对 size 翻转
            var b = takedImageView.bounds
            if idx % 2 != 0{
                let s = b.size.flip
                b.size = s
            }
            let croppedImage = try SEQuadrangleHelper.cropImage(in: b, with: img, quad: corners)

滤镜后,修改图片朝向

           var final = croppedImage
            // 如果 idx 是负数, idx % 4 是 负数 
            // 然后 idx % 4 + 4 ,必然为正数
            let k = (idx % 4 + 4) % 4
            switch k {
            case 1:
                // 修改图片朝向代码,与上面的类似,具体见 github repo
                final = croppedImage.left
            case 2:
                final = croppedImage.down
            case 3:
                final = croppedImage.right
            default:
                ()
            }
            takedImageView.image = final

补充,点击旋转按钮

@objc
    func rotateBtnClickX(){
        angleX -= 1
        // 看到的效果,就是调仿射变换 
        takedImageView.transform = CGAffineTransform(rotationAngle: ImgSingleAngle.time * angleX)
        
        let idx = Int(angleX)
        // 图片竖着,有一个 frame
        var inS = areaInner
        if idx % 2 != 0{
             // 图片横着,有一个不同的 frame
            inS = areaInnerRotateLeft
        }
        takedImageView.frame = inS
        cropView.refresh(frame: inS)
    }

github repo