Swift中旋转UIImage

67

我正在使用带有Swift的Xcode 6.0.1。我有一个UIImage,并且我想使用旧图像作为源来制作另一张图像,新图像在某种方式下被旋转...例如垂直翻转。

这个问题已经在几个月前得到了回答(链接)。但是,那个解决方案对我不起作用,即使情况完全相同。

当我有

var image = UIImage(CGImage: otherImage.CGImage, scale: 1.0, orientation: .DownMirrored)

Xcode 抱怨"调用中存在额外的参数 'scale'"。在查阅了苹果文档后,这毫无意义,因为该初始化程序版本确实使用了这三个参数。虽然省略比例和方向参数可以解决问题,但会阻止我进行旋转。

我能找到的唯一其他参考资料是这位遇到了相同的问题。

你觉得呢?

我需要在这个Xcode版本上运行它,所以如果有其他方式来执行旋转(我还没有找到),那将会很有用。


2
Xcode 6.0.1已经过时(不确定是否是问题所在)。您的代码在我的Xcode 6.1中编译没有错误或警告。只有当您省略.CGImage(如参考的github页面)时,才会出现编译器错误。 - Martin R
如果您愿意,在这个Q/A中,您可以轻松地旋转图像视图并绘制图像。https://dev59.com/XVgR5IYBdhLWcg3ww_ze - Khaled Annajar
13个回答

144

Swift 5解决方案

我编写了Pixel SDK,它为这个问题提供了更强大的解决方案,否则这里是最简单的解决方案:

extension UIImage {
    func rotate(radians: Float) -> UIImage? {
        var newSize = CGRect(origin: CGPoint.zero, size: self.size).applying(CGAffineTransform(rotationAngle: CGFloat(radians))).size
        // Trim off the extremely small float value to prevent core graphics from rounding it up
        newSize.width = floor(newSize.width)
        newSize.height = floor(newSize.height)
        
        UIGraphicsBeginImageContextWithOptions(newSize, false, self.scale)
        guard let context = UIGraphicsGetCurrentContext() else { return nil }
        
        // Move origin to middle
        context.translateBy(x: newSize.width/2, y: newSize.height/2)
        // Rotate around middle
        context.rotate(by: CGFloat(radians))
        // Draw the image at its center
        self.draw(in: CGRect(x: -self.size.width/2, y: -self.size.height/2, width: self.size.width, height: self.size.height))
    
        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        return newImage
    }
}

使用这个解决方案,您可以按照以下步骤进行操作。
let image = UIImage(named: "image.png")!
let newImage = image.rotate(radians: .pi/2) // Rotate 90 degrees

4
这是一个很棒的解决方案。我之前使用的另一个方案在旋转后剪裁了我的图像,但这个完美地工作了。 - Adela Toderici
2
处理具有不同方向的UIImage时要小心。self.draw()将自动以.up方向绘制图像。 - crom87
2
我正在尝试使用这个方法在自定义注释视图中旋转一个UIImage。该图像是一辆摩托车的.png文件。但它并没有旋转图像...它只是返回一个黑色正方形? - lozflan
9
.pi жШѓ 180 еЇ¶пЉМ.pi/2 жШѓ 90 еЇ¶пЉМ.pi/4 жШѓ 45 еЇ¶пЉМ...пЉМ.pi/180 жШѓ 1 еЇ¶гАВ - lenooh
2
如果您使用SwiftUI,请将SwiftUI导入到UIImage扩展中,而不是UIKit,并将函数签名更改为:rotate(angle:Angle)。在函数内部,执行:let radians = angle.radians。现在,您可以使用以下方式调用该函数:image.rotate(angle: .degrees(90))image.rotate(angle: .degrees(-90)) - P. Ent
显示剩余2条评论

48

这是一个简单的UIImage扩展:

//ImageRotation.swift

import UIKit

extension UIImage {  
    public func imageRotatedByDegrees(degrees: CGFloat, flip: Bool) -> UIImage {
        let radiansToDegrees: (CGFloat) -> CGFloat = {
            return $0 * (180.0 / CGFloat(M_PI))
        }
        let degreesToRadians: (CGFloat) -> CGFloat = {
            return $0 / 180.0 * CGFloat(M_PI)
        }

        // calculate the size of the rotated view's containing box for our drawing space
        let rotatedViewBox = UIView(frame: CGRect(origin: CGPointZero, size: size))
        let t = CGAffineTransformMakeRotation(degreesToRadians(degrees));
        rotatedViewBox.transform = t
        let rotatedSize = rotatedViewBox.frame.size

        // Create the bitmap context
        UIGraphicsBeginImageContext(rotatedSize)
        let bitmap = UIGraphicsGetCurrentContext()

        // Move the origin to the middle of the image so we will rotate and scale around the center.
        CGContextTranslateCTM(bitmap, rotatedSize.width / 2.0, rotatedSize.height / 2.0);

        //   // Rotate the image context
        CGContextRotateCTM(bitmap, degreesToRadians(degrees));

        // Now, draw the rotated/scaled image into the context
        var yFlip: CGFloat

        if(flip){
            yFlip = CGFloat(-1.0)
        } else {
            yFlip = CGFloat(1.0)
        }

        CGContextScaleCTM(bitmap, yFlip, -1.0)
        CGContextDrawImage(bitmap, CGRectMake(-size.width / 2, -size.height / 2, size.width, size.height), CGImage)

        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return newImage
    }
}

(来源)

与以下内容一起使用:

rotatedPhoto = rotatedPhoto?.imageRotatedByDegrees(90, flip: false) 

如果flip设置为true,则前者将旋转图像并翻转它。


4
Confile,翻转功能很棒。旋转的问题在于它会旋转图像,但是 - 它会改变图像的纵横比并且"破坏"它,请您检查并修改代码吗?这将非常好。谢谢! - Roi Mulia
2
UIGraphicsBeginImageContext(rotatedSize) 应该改为 UIGraphicsBeginImageContextWithOptions(rotatedSize, false, self.scale) 以保留比例。 - adrum
适用于我,一个不错的复制粘贴解决方案。 - chengsam
7
Xcode提供了将这段代码转换为Swift 3的建议,除了这一行代码:CGContextDrawImage(bitmap, CGRectMake(-size.width / 2, -size.height / 2, size.width, size.height), CGImage)。请将其更改为bitmap?.draw(cgImage!, in: CGRect(x: -size.width / 2, y: -size.height / 2, width: size.width, height: size.height)) - Hanny
1
这种方式不会释放内存。每次我旋转用 iPhone 6 拍摄的新照片时,它会保留大约30MB的RAM。 - t4nhpt
显示剩余4条评论

36
extension UIImage {
    struct RotationOptions: OptionSet {
        let rawValue: Int

        static let flipOnVerticalAxis = RotationOptions(rawValue: 1)
        static let flipOnHorizontalAxis = RotationOptions(rawValue: 2)
    }

    func rotated(by rotationAngle: Measurement<UnitAngle>, options: RotationOptions = []) -> UIImage? {
        guard let cgImage = self.cgImage else { return nil }

        let rotationInRadians = CGFloat(rotationAngle.converted(to: .radians).value)
        let transform = CGAffineTransform(rotationAngle: rotationInRadians)
        var rect = CGRect(origin: .zero, size: self.size).applying(transform)
        rect.origin = .zero

        let renderer = UIGraphicsImageRenderer(size: rect.size)
        return renderer.image { renderContext in
            renderContext.cgContext.translateBy(x: rect.midX, y: rect.midY)
            renderContext.cgContext.rotate(by: rotationInRadians)

            let x = options.contains(.flipOnVerticalAxis) ? -1.0 : 1.0
            let y = options.contains(.flipOnHorizontalAxis) ? 1.0 : -1.0
            renderContext.cgContext.scaleBy(x: CGFloat(x), y: CGFloat(y))

            let drawRect = CGRect(origin: CGPoint(x: -self.size.width/2, y: -self.size.height/2), size: self.size)
            renderContext.cgContext.draw(cgImage, in: drawRect)
        }
    }
}
你可以像这样使用它:
let rotatedImage = UIImage(named: "my_amazing_image")?.rotated(by: Measurement(value: 48.0, unit: .degrees))

使用指定的翻转标志:

let flippedImage = UIImage(named: "my_amazing_image")?.rotated(by: Measurement(value: 48.0, unit: .degrees), options: [.flipOnVerticalAxis])

谢谢,非常好的解决方案。你能否也提供一个翻转图像的解决方案? - ixany
1
@ixany,我已经更新了答案,以在垂直轴上翻转。这是你想要的吗? - Stefan Church
那正是我正在寻找的。再次感谢你! :) - ixany
1
我的研究到此结束,谢谢! - SThakur
这个函数存在严重问题。当imageOrientation != UIImage.Orientation.up时,该函数会给出完全错误的图像。 - Marcin Olawski
为了保持旋转后图像的大小(可能导致被裁剪),请删除.applying(transform) - mikro098

11

Swift 3:

向右旋转:

let image = UIImage(cgImage: otherImage.cgImage!, scale: CGFloat(1.0), orientation: .right)

在游乐场中测试:

// MyPlayground.playground

import UIKit
import PlaygroundSupport

let view = UIView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
view.backgroundColor = UIColor.white
PlaygroundPage.current.liveView = view

let otherImage = UIImage(named: "burger.png", in: Bundle.main,
                         compatibleWith: nil)
let imageViewLeft = UIImageView(image: UIImage(cgImage: (otherImage?.cgImage!)!, scale: CGFloat(1.0), orientation: .left)) 
let imageViewRight = UIImageView(image: UIImage(cgImage: (otherImage?.cgImage!)!, scale: CGFloat(1.0), orientation: .right))

view.addSubview(imageViewLeft)
view.addSubview(imageViewRight)
view.layoutIfNeeded()

为什么不呢?给我看看你的 Playground 文件! - Peter Kreinz
这应该是答案!谢谢! - arvidurs
这并不是回答问题的方法,因为他们要求一种制作另一张图像的方法。这个解决方案只使用方向EXIF数据来将图像显示为右转。所以,例如,你不能调用这个两次来旋转图像180度。 - Typewriter
@typewrite: 你是对的,但这有助于旋转UIImages的其他人。 - Peter Kreinz

10

'调用中的额外参数'通常是由于其中一个输入类型不正确造成的。

您可以通过以下方法修复代码

var image = UIImage(CGImage: otherImage.CGImage, scale: CGFloat(1.0), orientation: .DownMirrored)

7
请勿使用UIGraphicsBeginImageContext,因为它会破坏您的向量质量,请使用UIGraphicsBeginImageContextWithOptions。这是我在Swift 3/4中的实现:
extension UIImage {

  func rotated(degrees: CGFloat) -> UIImage? {

    let degreesToRadians: (CGFloat) -> CGFloat = { (degrees: CGFloat) in
      return degrees / 180.0 * CGFloat.pi
    }

    // Calculate the size of the rotated view's containing box for our drawing space
    let rotatedViewBox: UIView = UIView(frame: CGRect(origin: .zero, size: size))
    rotatedViewBox.transform = CGAffineTransform(rotationAngle: degreesToRadians(degrees))
    let rotatedSize: CGSize = rotatedViewBox.frame.size

    // Create the bitmap context
    UIGraphicsBeginImageContextWithOptions(rotatedSize, false, 0.0)

    guard let bitmap: CGContext = UIGraphicsGetCurrentContext(), let unwrappedCgImage: CGImage = cgImage else {
      return nil
    }

    // Move the origin to the middle of the image so we will rotate and scale around the center.
    bitmap.translateBy(x: rotatedSize.width/2.0, y: rotatedSize.height/2.0)

    // Rotate the image context
    bitmap.rotate(by: degreesToRadians(degrees))

    bitmap.scaleBy(x: CGFloat(1.0), y: -1.0)

    let rect: CGRect = CGRect(
        x: -size.width/2,
        y: -size.height/2,
        width: size.width,
        height: size.height)

    bitmap.draw(unwrappedCgImage, in: rect)

    guard let newImage: UIImage = UIGraphicsGetImageFromCurrentImageContext() else {
      return nil
    }

    UIGraphicsEndImageContext()

    return newImage
  }

我该如何使用这个扩展程序。我尝试这样使用它:let originalImage = UIImage(named: "image").rotated(degrees: 90),但是没有起作用。 - fabiobh
什么没成功?是编译失败了还是没有按预期工作?你在使用向量吗? - Pedro Paulo Amorim

7

@confile的答案已更新至Swift 4

import UIKit

extension UIImage {

    public func imageRotatedByDegrees(degrees: CGFloat, flip: Bool) -> UIImage {
        let radiansToDegrees: (CGFloat) -> CGFloat = {
            return $0 * (180.0 / CGFloat.pi)
        }
        let degreesToRadians: (CGFloat) -> CGFloat = {
            return $0 / 180.0 * CGFloat.pi
        }

        // calculate the size of the rotated view's containing box for our drawing space
        let rotatedViewBox = UIView(frame: CGRect(origin: .zero, size: size))
        let t = CGAffineTransform(rotationAngle: degreesToRadians(degrees));
        rotatedViewBox.transform = t
        let rotatedSize = rotatedViewBox.frame.size

        // Create the bitmap context
        UIGraphicsBeginImageContext(rotatedSize)
        let bitmap = UIGraphicsGetCurrentContext()

        // Move the origin to the middle of the image so we will rotate and scale around the center.
        bitmap?.translateBy(x: rotatedSize.width / 2.0, y: rotatedSize.height / 2.0)

        //   // Rotate the image context
        bitmap?.rotate(by: degreesToRadians(degrees))

        // Now, draw the rotated/scaled image into the context
        var yFlip: CGFloat

        if(flip){
            yFlip = CGFloat(-1.0)
        } else {
            yFlip = CGFloat(1.0)
        }

        bitmap?.scaleBy(x: yFlip, y: -1.0)
        let rect = CGRect(x: -size.width / 2, y: -size.height / 2, width: size.width, height: size.height)

        bitmap?.draw(cgImage!, in: rect)

        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return newImage!
    }
}

5

试试这段代码,它对我有效:

@IBAction func btnRotateImagePressed(sender: AnyObject) {
    if let originalImage = self.imageView.image {

        let rotateSize = CGSize(width: originalImage.size.height, height: originalImage.size.width)
        UIGraphicsBeginImageContextWithOptions(rotateSize, true, 2.0)
        if let context = UIGraphicsGetCurrentContext() {
            CGContextRotateCTM(context, 90.0 * CGFloat(M_PI) / 180.0)
            CGContextTranslateCTM(context, 0, -originalImage.size.height)
            originalImage.drawInRect(CGRectMake(0, 0, originalImage.size.width, originalImage.size.height))
            self.imageView.image = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()
        }
    }
}

我将它转换成了Swift4,但是它导致了内存问题。 - Hope
使用 UIGraphicsEndImageContext() 后,内存问题得到了解决。 - Hope
当捕获横向左侧图像时,显示的图像是错误的一面。 - Ravi Padsala

2

使用 Swift 5 和 Xcode 13 进行测试

extension UIView{

func rotate(degrees: CGFloat) {

        let degreesToRadians: (CGFloat) -> CGFloat = { (degrees: CGFloat) in
            return degrees / 180.0 * CGFloat.pi
        }
        self.transform =  CGAffineTransform(rotationAngle: degreesToRadians(degrees))
    }

只需传递角度值(以度为单位)

myView.rotate(degrees : 90)

这会旋转一个 UIView 而不是一个 UIImage - undefined

2

如果您需要水平翻转图像或需要原始图像的镜像图像,有一个简单的方法:

以下代码可用于翻转图像:

let originalImage = UIImage(named: "image")
let flippedImage = originalImage?.withHorizontallyFlippedOrientation()

Documentation


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接