web-dev-qa-db-ja.com

画像をCGImageに変換中に画像の向きが失われる

長方形の元の画像から画像の正方形の部分を切り抜くときに、画像の向きの問題に直面しています。画像が横向きの場合は問題ありません。しかし、縦向きの場合、画像の向きが保持されていないように見えます。その結果、画像の向きが間違っていて、トリミングが悪くなります。

 func cropImage(cropRectangleCoordinates: CGRect) {

        let croppedImage = originalImage

        let finalCroppedImage : CGImageRef = CGImageCreateWithImageInRect(croppedImage.CGImage, cropRectangleCoordinates)

        finalImage =  UIImage(CGImage: finalCroppedImage)!


    }

問題はcroppedImage.CGImageにあると思います。ここで画像はCGImageに変換されますが、向きが保持されていないようです。 UIImageのみを使用することで向きを維持するのは簡単ですが、トリミングを行うには、画像を一時的にCGImageにする必要があり、これが問題です。 UIImageに戻すときに画像の向きを変えても、正しい向きになっている可能性がありますが、CGImageをトリミングするとすでに損傷が発生しています。

これはSwiftの質問です。Objective-Cでは解決策が異なる可能性があるため、Swiftで回答してください。

15
Robert Brax

私は解決策を見つけました....それが十分に堅牢であるかどうかは時間がわかりますが、すべての状況で機能するようです。それは修正すべき悪質なバグでした。

したがって、問題は、UIImageが、場合によっては、CGImageに変換されたときに方向を失うことです。自動的に横向きモードになるポートレート画像に影響します。したがって、デフォルトで横向きの画像は影響を受けません。しかし、バグが悪質なのは、すべてのポートレート画像に影響を与えないことです。また、imageorientation値は、一部の画像には役立ちません。これらの問題のある画像は、ユーザーが電子メールやメッセージから取得した、またはWebから保存したライブラリにあるため、カメラで撮影されていない画像です。これらの画像には向き情報がない可能性があるため、場合によっては縦向きの画像.... CGImageに変換しても縦向きのままになります。デバイスライブラリ内の画像の一部がメッセージや電子メールから保存されていることに気付くまで、私は本当にそれに固執していました。

したがって、どの画像の向きが変わるかを推測するために私が見つけた唯一の信頼できる方法は、特定の画像の両方のバージョン(UIImageとCGImage)を作成し、それらの高さの値を比較することです。それらが等しい場合、CGImageバージョンは回転されず、期待どおりに操作できます。ただし、高さが異なる場合は、CGImageCreateWithImageInRectからのCGImage変換によって画像が造園されることを確認できます。この場合のみ、原点のx/y座標を交換します。これは、トリミングするために長方形の座標として渡し、それらの特別な画像を正しく処理します。

それは長い投稿でしたが、主なアイデアはCGImageの高さとUIImageの幅を比較することであり、それらが異なる場合は、原点が反転することを期待します。

6
Robert Brax

これは、他の人が書いた古いコードをいくつか見てから書いたUIImage拡張機能です。 Swift 3で記述され、iOSのorientationプロパティとCGAffineTransformを使用して、画像を適切な方向に再描画します。

Swift 3:

public extension UIImage {

    /// Extension to fix orientation of an UIImage without EXIF
    func fixOrientation() -> UIImage {

        guard let cgImage = cgImage else { return self }

        if imageOrientation == .up { return self }

        var transform = CGAffineTransform.identity

        switch imageOrientation {

        case .down, .downMirrored:
            transform = transform.translatedBy(x: size.width, y: size.height)
            transform = transform.rotated(by: CGFloat(M_PI))

        case .left, .leftMirrored:
            transform = transform.translatedBy(x: size.width, y: 0)
            transform = transform.rotated(by: CGFloat(M_PI_2))

        case .right, .rightMirrored:
            transform = transform.translatedBy(x: 0, y: size.height)
            transform = transform.rotated(by: CGFloat(-M_PI_2))

        case .up, .upMirrored:
            break
        }

        switch imageOrientation {

        case .upMirrored, .downMirrored:
            transform.translatedBy(x: size.width, y: 0)
            transform.scaledBy(x: -1, y: 1)

        case .leftMirrored, .rightMirrored:
            transform.translatedBy(x: size.height, y: 0)
            transform.scaledBy(x: -1, y: 1)

        case .up, .down, .left, .right:
            break
        }

        if let ctx = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, space: cgImage.colorSpace!, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) {

            ctx.concatenate(transform)

            switch imageOrientation {

            case .left, .leftMirrored, .right, .rightMirrored:
                ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.height, height: size.width))

            default:
                ctx.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
            }

            if let finalImage = ctx.makeImage() {
                return (UIImage(cgImage: finalImage))
            }
        }

        // something failed -- return original
        return self
    }
}
12
jeff-ziligy

Swift 3:

この方法で回転したcgImageをUIImageに変換します

UIImage(cgImage:croppedCGImage, scale:originalImage.scale, orientation:originalImage.imageOrientation)

ソース@デビッドベリーの答え

12
Adnan Yusuf

UIImage作成呼び出しを次のように変更します。

finalImage = UIImage(CGImage:finalCroppedImage, scale:originalImage.scale, orientation:originalImage.orientation)

画像の元の向き(およびスケール)を維持します。

5
David Berry

これが答えです。@ awolfの功績です( IImageのトリミング )。スケールと向きを完全に処理します。トリミングする画像でこのメソッドを呼び出すだけで、縮尺や向きを気にせずにトリミングCGRectを渡すことができます。ここで行ったように強制的にアンラップするのではなく、cgImageがnilであるかどうかを自由に確認してください。

extension UIImage {
    func croppedInRect(rect: CGRect) -> UIImage {
        func rad(_ degree: Double) -> CGFloat {
            return CGFloat(degree / 180.0 * .pi)
        }

        var rectTransform: CGAffineTransform
        switch imageOrientation {
        case .left:
            rectTransform = CGAffineTransform(rotationAngle: rad(90)).translatedBy(x: 0, y: -self.size.height)
        case .right:
            rectTransform = CGAffineTransform(rotationAngle: rad(-90)).translatedBy(x: -self.size.width, y: 0)
        case .down:
            rectTransform = CGAffineTransform(rotationAngle: rad(-180)).translatedBy(x: -self.size.width, y: -self.size.height)
        default:
            rectTransform = .identity
        }
        rectTransform = rectTransform.scaledBy(x: self.scale, y: self.scale)

        let imageRef = self.cgImage!.cropping(to: rect.applying(rectTransform))
        let result = UIImage(cgImage: imageRef!, scale: self.scale, orientation: self.imageOrientation)
        return result
    }
}

別の注意:imageViewに埋め込まれたscrollViewを使用している場合は、追加の手順が1つあり、ズーム率を考慮する必要があります。 imageViewscrollViewのコンテンツビュー全体にまたがり、scrollViewの境界をトリミングフレームとして使用すると、トリミングされた画像は次のように取得できます。

let ratio = imageView.image!.size.height / scrollView.contentSize.height
let Origin = CGPoint(x: scrollView.contentOffset.x * ratio, y: scrollView.contentOffset.y * ratio)
let size = CGSize(width: scrollView.bounds.size.width * ratio, let height: scrollView.bounds.size.height * ratio)
let cropFrame = CGRect(Origin: Origin, size: size)
let croppedImage = imageView.image!.croppedInRect(rect: cropFrame)
3
JGuo

@JGuoは実際に機能した唯一の答えを持っています。オプションのUIImage?を返すように、またSwift-er構文用に少しだけ更新しました。私は暗黙のうちにアンラップしないことを好みます。

extension UIImage {

    func crop(to rect: CGRect) -> UIImage? {
        func rad(_ degree: Double) -> CGFloat {
            return CGFloat(degree / 180.0 * .pi)
        }

        var rectTransform: CGAffineTransform
        switch imageOrientation {
        case .left:
            rectTransform = CGAffineTransform(rotationAngle: rad(90)).translatedBy(x: 0, y: -self.size.height)
        case .right:
            rectTransform = CGAffineTransform(rotationAngle: rad(-90)).translatedBy(x: -self.size.width, y: 0)
        case .down:
            rectTransform = CGAffineTransform(rotationAngle: rad(-180)).translatedBy(x: -self.size.width, y: -self.size.height)
        default:
            rectTransform = .identity
        }
        rectTransform = rectTransform.scaledBy(x: self.scale, y: self.scale)

        guard let imageRef = self.cgImage?.cropping(to: rect.applying(rectTransform)) else { return nil }
        let result = UIImage(cgImage: imageRef, scale: self.scale, orientation: self.imageOrientation)
        return result
    }
}

これが私のViewControllerの計算されたプロパティとしての実装です。

var croppedImage: UIImage? {
    guard let image = self.image else { return nil }

    let ratio = image.size.height / self.contentSize.height
    let Origin = CGPoint(x: self.contentOffset.x * ratio, y: self.contentOffset.y * ratio)
    let size = CGSize(width: self.bounds.size.width * ratio, height: self.bounds.size.height * ratio)
    let cropFrame = CGRect(Origin: Origin, size: size)
    let croppedImage = image.crop(to: cropFrame)

    return croppedImage
}
1
Alex Wall