在Swift、Cocoa、Mac OSX中旋转NSImage

4

在Mac OSX应用程序中,有没有简单的方法来旋转NSImage?或者只是使用Swift将方向从纵向设置为横向?

我正在尝试使用CATransform3DMakeAffineTransform进行操作,但无法使其正常工作。

CATransform3DMakeAffineTransform(CGAffineTransformMakeRotation(CGFloat(M_PI) * 90/180))

这是我第一次使用转换技术,所以请耐心等待 :) 也许我正在采用错误的方法...

有人能帮帮我吗?

谢谢!

6个回答

12
public extension NSImage {
public func imageRotatedByDegreess(degrees:CGFloat) -> NSImage {

    var imageBounds = NSZeroRect ; imageBounds.size = self.size
    let pathBounds = NSBezierPath(rect: imageBounds)
    var transform = NSAffineTransform()
    transform.rotateByDegrees(degrees)
    pathBounds.transformUsingAffineTransform(transform)
    let rotatedBounds:NSRect = NSMakeRect(NSZeroPoint.x, NSZeroPoint.y, pathBounds.bounds.size.width, pathBounds.bounds.size.height )
    let rotatedImage = NSImage(size: rotatedBounds.size)

    //Center the image within the rotated bounds
    imageBounds.origin.x = NSMidX(rotatedBounds) - (NSWidth(imageBounds) / 2)
    imageBounds.origin.y  = NSMidY(rotatedBounds) - (NSHeight(imageBounds) / 2)

    // Start a new transform
    transform = NSAffineTransform()
    // Move coordinate system to the center (since we want to rotate around the center)
    transform.translateXBy(+(NSWidth(rotatedBounds) / 2 ), yBy: +(NSHeight(rotatedBounds) / 2))
    transform.rotateByDegrees(degrees)
    // Move the coordinate system bak to normal 
    transform.translateXBy(-(NSWidth(rotatedBounds) / 2 ), yBy: -(NSHeight(rotatedBounds) / 2))
    // Draw the original image, rotated, into the new image
    rotatedImage.lockFocus()
    transform.concat()
    self.drawInRect(imageBounds, fromRect: NSZeroRect, operation: NSCompositingOperation.CompositeCopy, fraction: 1.0)
    rotatedImage.unlockFocus()

    return rotatedImage
}


var image = NSImage(named:"test.png")!.imageRotatedByDegreess(CGFloat(90))  //use only this values 90, 180, or 270
}

更新至 Swift 3:

public extension NSImage {
public func imageRotatedByDegreess(degrees:CGFloat) -> NSImage {

    var imageBounds = NSZeroRect ; imageBounds.size = self.size
    let pathBounds = NSBezierPath(rect: imageBounds)
    var transform = NSAffineTransform()
    transform.rotate(byDegrees: degrees)
    pathBounds.transform(using: transform as AffineTransform)
    let rotatedBounds:NSRect = NSMakeRect(NSZeroPoint.x, NSZeroPoint.y, pathBounds.bounds.size.width, pathBounds.bounds.size.height )
    let rotatedImage = NSImage(size: rotatedBounds.size)

    //Center the image within the rotated bounds
    imageBounds.origin.x = NSMidX(rotatedBounds) - (NSWidth(imageBounds) / 2)
    imageBounds.origin.y  = NSMidY(rotatedBounds) - (NSHeight(imageBounds) / 2)

    // Start a new transform
    transform = NSAffineTransform()
    // Move coordinate system to the center (since we want to rotate around the center)
    transform.translateX(by: +(NSWidth(rotatedBounds) / 2 ), yBy: +(NSHeight(rotatedBounds) / 2))
    transform.rotate(byDegrees: degrees)
    // Move the coordinate system bak to normal
    transform.translateX(by: -(NSWidth(rotatedBounds) / 2 ), yBy: -(NSHeight(rotatedBounds) / 2))
    // Draw the original image, rotated, into the new image
    rotatedImage.lockFocus()
    transform.concat()
    self.draw(in: imageBounds, from: NSZeroRect, operation: NSCompositingOperation.copy, fraction: 1.0)
    rotatedImage.unlockFocus()

    return rotatedImage
    }
}

class SomeClass: NSViewController {
       var image = NSImage(named:"test.png")!.imageRotatedByDegreess(degrees: CGFloat(90))  //use only this values 90, 180, or 270
}

非常感谢。这很完美 :) - stefOCDP
1
很遗憾,由于您没有在任何地方使用转换后的“pathBounds”(请参见下面的答案),因此它无法工作。 - user187676
这个解决方案对我也适用,但不幸的是旋转图像质量很差 :/ - Nininea

5

感谢你提供的解决方案,但对我来说并没有完美地运行。 正如您可能已经注意到的那样,pathBounds 并没有被任何地方使用。在我看来,它应该像这样使用:

let rotatedBounds:NSRect = NSMakeRect(NSZeroPoint.x, NSZeroPoint.y , pathBounds.bounds.size.width, pathBounds.bounds.size.height )

否则,图像将被旋转但会被裁剪为正方形边界。

你能展示一下你的解决方案如何使用吗?仅凭这个还不能旋转图像。 - Rutger Huijsmans

4

让IKImageView来完成繁重的工作:

import Quartz

extension NSImage {
    func imageRotated(by degrees: CGFloat) -> NSImage {
        let imageRotator = IKImageView()
        var imageRect = CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height)
        let cgImage = self.cgImage(forProposedRect: &imageRect, context: nil, hints: nil)
        imageRotator.setImage(cgImage, imageProperties: [:])
        imageRotator.rotationAngle = CGFloat(-(degrees / 180) * CGFloat(M_PI))
        let rotatedCGImage = imageRotator.image().takeUnretainedValue()
        return NSImage(cgImage: rotatedCGImage, size: NSSize.zero)
    }
}

你有没有想过如何通过rotateImageLeft / rotateImageRight使其工作?我猜这两个函数并没有真正进行图像旋转,而是更新了一些方向标签。 - silverdr

4
这里提供一个简单的Swift(4+)方案,用于绘制以中心为轴旋转的图像:
extension NSImage {
    /// Rotates the image by the specified degrees around the center.
    /// Note that if the angle is not a multiple of 90°, parts of the rotated image may be drawn outside the image bounds.
    func rotated(by angle: CGFloat) -> NSImage {
        let img = NSImage(size: self.size, flipped: false, drawingHandler: { (rect) -> Bool in
            let (width, height) = (rect.size.width, rect.size.height)
            let transform = NSAffineTransform()
            transform.translateX(by: width / 2, yBy: height / 2)
            transform.rotate(byDegrees: angle)
            transform.translateX(by: -width / 2, yBy: -height / 2)
            transform.concat()
            self.draw(in: rect)
            return true
        })
        img.isTemplate = self.isTemplate // preserve the underlying image's template setting
        return img
    }
}

谢谢,这很好,但只适用于正方形图像。矩形的图像会被裁剪。 - OwlOCR

4
这个功能也适用于非正方形图片,Swift 5。
extension NSImage {
    func rotated(by degrees : CGFloat) -> NSImage {
        var imageBounds = NSRect(x: 0, y: 0, width: size.width, height: size.height)
        let rotatedSize = AffineTransform(rotationByDegrees: degrees).transform(size)
        let newSize = CGSize(width: abs(rotatedSize.width), height: abs(rotatedSize.height))
        let rotatedImage = NSImage(size: newSize)

        imageBounds.origin = CGPoint(x: newSize.width / 2 - imageBounds.width / 2, y: newSize.height / 2 - imageBounds.height / 2)

        let otherTransform = NSAffineTransform()
        otherTransform.translateX(by: newSize.width / 2, yBy: newSize.height / 2)
        otherTransform.rotate(byDegrees: degrees)
        otherTransform.translateX(by: -newSize.width / 2, yBy: -newSize.height / 2)

        rotatedImage.lockFocus()
        otherTransform.concat()
        draw(in: imageBounds, from: CGRect.zero, operation: NSCompositingOperation.copy, fraction: 1.0)
        rotatedImage.unlockFocus()

        return rotatedImage
    }
}

代码很好,但它实际上在一个方向上裁剪,而在另一个方向上正确地扩展。问题出在AffineTransform()旋转上。 - LGP

3

在@FrankByte.com的代码基础上,这个版本应该在任何图像的x和y方向上正确扩展,并且支持任何旋转。

extension NSImage {
    func rotated(by degrees: CGFloat) -> NSImage {
        let sinDegrees = abs(sin(degrees * CGFloat.pi / 180.0))
        let cosDegrees = abs(cos(degrees * CGFloat.pi / 180.0))
        let newSize = CGSize(width: size.height * sinDegrees + size.width * cosDegrees,
                             height: size.width * sinDegrees + size.height * cosDegrees)

        let imageBounds = NSRect(x: (newSize.width - size.width) / 2,
                                 y: (newSize.height - size.height) / 2,
                                 width: size.width, height: size.height)

        let otherTransform = NSAffineTransform()
        otherTransform.translateX(by: newSize.width / 2, yBy: newSize.height / 2)
        otherTransform.rotate(byDegrees: degrees)
        otherTransform.translateX(by: -newSize.width / 2, yBy: -newSize.height / 2)

        let rotatedImage = NSImage(size: newSize)
        rotatedImage.lockFocus()
        otherTransform.concat()
        draw(in: imageBounds, from: CGRect.zero, operation: NSCompositingOperation.copy, fraction: 1.0)
        rotatedImage.unlockFocus()

        return rotatedImage
    }
}

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