Потеря ориентации изображения при преобразовании изображения в 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 ущерб уже нанесен.

Это быстрый вопрос, поэтому, пожалуйста, ответьте быстро, поскольку в Objective-C решение может быть другим.


person Robert Brax    schedule 12.02.2015    source источник


Ответы (7)


SWIFT 3:

преобразовать повернутый cgImage в UIImage этим методом

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

Источник @ Дэвид Берри ответ

person Adnan Yusuf    schedule 10.01.2017
comment
Спасибо :) Это намного лучше, чем те 100 строк кода вращения, которые я видел в других ответах: D - person Matthew Cawley; 04.12.2017

Вот расширение UIImage, которое я написал после просмотра нескольких старых фрагментов кода, написанных другими. Он написан на Swift 3 и использует свойство ориентации iOS плюс 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
    }
}
person ziligy    schedule 05.11.2016
comment
вызов этого перед преобразованием в вызов image.cgImage работал - person Cjay; 01.03.2018

Я нашел решение ... время покажет, достаточно ли оно надежно, но, похоже, оно работает во всех ситуациях. Это была серьезная ошибка, которую нужно было исправить.

Проблема в том, что UIImage, только в некоторых случаях, теряет ориентацию при преобразовании в CGImage. Влияет на изображение портретов, которые автоматически переводятся в альбомный режим. Таким образом, изображения, которые по умолчанию являются альбомными, не затрагиваются. Но самая большая ошибка заключается в том, что она не влияет на ВСЕ портретные изображения !! Также значение imageorientation не поможет для некоторых изображений. Эти проблемные изображения - это изображения, которые пользователь имеет в своей библиотеке, которые он получил из электронной почты, сообщений или сохраненных из Интернета, поэтому не были сняты с помощью камеры. Эти изображения, возможно, не имеют информации об ориентации, и, следовательно, в некоторых случаях изображение в портретной ориентации ... ОСТАЕТСЯ в портретной ориентации при преобразовании в CGImage. Я действительно застрял на этом, пока не понял, что некоторые из моих изображений в моей библиотеке устройства были сохранены из сообщений или электронных писем.

Итак, единственный надежный способ угадать, какое изображение будет переориентировано, - это создать обе версии данного изображения: UIImage и CGImage и сравнить их значения высоты. Если они равны, версия CGImage не будет повернута, и вы сможете работать с ней должным образом. Но если у них разная высота, вы можете быть уверены, что преобразование CGImage из CGImageCreateWithImageInRect улучшит ландшафт изображения. Только в этом случае я меняю местами координаты x / y источника, которые я передаю как координату прямоугольника для обрезки, и он правильно обрабатывает эти специальные изображения.

Это был длинный пост, но основная идея - сравнить высоту CGImage с шириной UIImage, и, если они разные, ожидать, что исходная точка будет инвертирована.

person Robert Brax    schedule 12.02.2015
comment
Но если изображение было квадратным, это будет большой проблемой для вашего решения: [] - person Husam; 14.12.2015
comment
Просто укажите путь к изображению, как показано ниже, и загрузите изображение UIImageView * imgViewTemp = [[UIImageView alloc] init]; [imgViewTemp setImage: [UIImage imageWithCGImage: [rep fullScreenImage]]]; UIImage * compressedImage = [mYGlobal funResizing: imgViewTemp.image]; У меня такая же проблема, но исправьте, как указано выше. - person chetu; 24.01.2017

Это ОТВЕТ, спасибо @awolf (Обрезка UIImage). Прекрасно справляется с масштабированием и ориентацией. Просто вызовите этот метод для изображения, которое вы хотите обрезать, и передайте кадрирование CGRect, не беспокоясь о масштабе или ориентации. Не стесняйтесь проверить, равно ли cgImage нулю, вместо того, чтобы принудительно развернуть его, как я сделал здесь.

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, есть один дополнительный шаг, вы должны принять во внимание коэффициент масштабирования. Предполагая, что ваш imageView охватывает все представление содержимого scrollView, и вы используете границы 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)
person Jack Guo    schedule 10.03.2018
comment
Большое спасибо, я использовал ваше расширение и часть своего кода, и это отлично работает - person Gertjan Brouwer; 15.08.2018
comment
Хороший ответ, дружище! - person Artem Kirillov; 13.08.2020

Измените вызов создания UIImage на:

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

для сохранения исходной ориентации (и масштаба) изображения.

person David Berry    schedule 12.02.2015
comment
Я пробовал это, но проблема более серьезная: finalimage уже обрезан, потеря ориентации происходит на более ранней стадии: croppedImage.CGImage. То, что вы предлагаете, может правильно повернуть изображение, но после того, как будет нанесен ущерб. - person Robert Brax; 12.02.2015
comment
Большое спасибо. Это мне поможет. - person Eugene Trapeznikov; 09.08.2017

У @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
}
person Alex Wall    schedule 06.12.2018

SWIFT 5. Я добавил следующее как расширение UIImage. Идея состоит в том, чтобы заставить изображение внутри вашего UIImage соответствовать ориентации UIImage (которая играет роль только в том, как оно отображается). Перерисовка фактических данных изображения внутри контейнера UIImage приведет к тому, что соответствующий CGImage будет иметь ту же ориентацию.

func forceSameOrientation() -> UIImage {
    UIGraphicsBeginImageContext(self.size)
    self.draw(in: CGRect(origin: CGPoint.zero, size: self.size))
    guard let image = UIGraphicsGetImageFromCurrentImageContext() else {
        UIGraphicsEndImageContext()
        return self
    }
    UIGraphicsEndImageContext()
    return image
}
person Kaccie Li    schedule 08.06.2020