[英]Rotating a CGImage
我有一個從 UIImagePickerController 獲取的 UIImage。 當我從- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
方法收到圖像時,我將它直接放入 UIImageView 進行預覽,並為用戶提供保存或丟棄圖像的選項。
當用戶選擇保存選項時,應用程序會獲取圖像的 png 數據並將其保存到它的文檔目錄中。 但是在兩種可能的設備方向之一(僅限橫向)下發生的情況是圖像被顛倒保存(更具體地說,從它應該的位置旋轉 180 度)。 所以當我在圖庫中加載圖像時,它看起來是顛倒的。
(請參閱圖庫中的左下角圖片)
我已經解決了這個問題,UIImagePickerController 中 UIImage 中的原始圖像數據沒有旋轉,而是將方向存儲為對象的一個屬性,並且僅在顯示時應用。 所以我試圖使用我在網上找到的一些代碼來旋轉與 UIImage 關聯的 CGImage 。 但它似乎對圖像完全沒有影響。 我使用的代碼如下:
- (void)rotateImage:(UIImage*)image byRadians:(CGFloat)rads
{
// calculate the size of the rotated view's containing box for our drawing space
UIView *rotatedViewBox = [[UIView alloc] initWithFrame:CGRectMake(0,0,image.size.width, image.size.height)];
CGAffineTransform t = CGAffineTransformMakeRotation(rads);
rotatedViewBox.transform = t;
CGSize rotatedSize = rotatedViewBox.frame.size;
// Create the bitmap context
UIGraphicsBeginImageContext(rotatedSize);
CGContextRef bitmap = UIGraphicsGetCurrentContext();
// Move the origin to the middle of the image you want to rotate and scale around the center.
CGContextTranslateCTM(bitmap, rotatedSize.width/2, rotatedSize.height/2);
// Rotate the image context
CGContextRotateCTM(bitmap, rads);
// Now, draw the rotated/scaled image into the context
CGContextScaleCTM(bitmap, 1.0, -1.0);
CGContextDrawImage(bitmap, CGRectMake(image.size.width / 2, image.size.height / 2, image.size.width, image.size.height), [image CGImage]);
image = UIGraphicsGetImageFromCurrentImageContext();
image = [UIImage imageWithCGImage:image.CGImage scale:1.0 orientation:UIImageOrientationDown];
UIGraphicsEndImageContext();
}
我想知道為什么這段代碼不起作用,因為我在網上找到的每段代碼都無法進行圖像旋轉。
-(UIImage*) rotate:(UIImage*) src andOrientation:(UIImageOrientation)orientation
{
UIGraphicsBeginImageContext(src.size);
CGContextRef context=(UIGraphicsGetCurrentContext());
if (orientation == UIImageOrientationRight) {
CGContextRotateCTM (context, 90/180*M_PI) ;
} else if (orientation == UIImageOrientationLeft) {
CGContextRotateCTM (context, -90/180*M_PI);
} else if (orientation == UIImageOrientationDown) {
// NOTHING
} else if (orientation == UIImageOrientationUp) {
CGContextRotateCTM (context, 90/180*M_PI);
}
[src drawAtPoint:CGPointMake(0, 0)];
UIImage *img=UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
使用這個..它完美無缺! 確保在從 UIImagePicker 中選取圖像時使用此功能:
例子:
UIImage *image;
image = [self rotate:image andOrientation:image.imageOrientation];
確保在從文檔目錄中選擇照片的地方使用此功能。
我最近想更正由CGImage
支持的UIImage
以匹配所需的方向,而不是依賴 API 來尊重 UIImageOrientation 參數。
func createMatchingBackingDataWithImage(imageRef: CGImage?, orienation: UIImageOrientation) -> CGImage?
{
var orientedImage: CGImage?
if let imageRef = imageRef {
let originalWidth = imageRef.width
let originalHeight = imageRef.height
let bitsPerComponent = imageRef.bitsPerComponent
let bytesPerRow = imageRef.bytesPerRow
let bitmapInfo = imageRef.bitmapInfo
guard let colorSpace = imageRef.colorSpace else {
return nil
}
var degreesToRotate: Double
var swapWidthHeight: Bool
var mirrored: Bool
switch orienation {
case .up:
degreesToRotate = 0.0
swapWidthHeight = false
mirrored = false
break
case .upMirrored:
degreesToRotate = 0.0
swapWidthHeight = false
mirrored = true
break
case .right:
degreesToRotate = 90.0
swapWidthHeight = true
mirrored = false
break
case .rightMirrored:
degreesToRotate = 90.0
swapWidthHeight = true
mirrored = true
break
case .down:
degreesToRotate = 180.0
swapWidthHeight = false
mirrored = false
break
case .downMirrored:
degreesToRotate = 180.0
swapWidthHeight = false
mirrored = true
break
case .left:
degreesToRotate = -90.0
swapWidthHeight = true
mirrored = false
break
case .leftMirrored:
degreesToRotate = -90.0
swapWidthHeight = true
mirrored = true
break
}
let radians = degreesToRotate * Double.pi / 180.0
var width: Int
var height: Int
if swapWidthHeight {
width = originalHeight
height = originalWidth
} else {
width = originalWidth
height = originalHeight
}
let contextRef = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)
contextRef?.translateBy(x: CGFloat(width) / 2.0, y: CGFloat(height) / 2.0)
if mirrored {
contextRef?.scaleBy(x: -1.0, y: 1.0)
}
contextRef?.rotate(by: CGFloat(radians))
if swapWidthHeight {
contextRef?.translateBy(x: -CGFloat(height) / 2.0, y: -CGFloat(width) / 2.0)
} else {
contextRef?.translateBy(x: -CGFloat(width) / 2.0, y: -CGFloat(height) / 2.0)
}
contextRef?.draw(imageRef, in: CGRect(x: 0.0, y: 0.0, width: CGFloat(originalWidth), height: CGFloat(originalHeight)))
orientedImage = contextRef?.makeImage()
}
return orientedImage
}
我有你的問題。你必須使用CGContextTranslateCTM(context, -rotatedSize.width/2, -rotatedSize.height/2);
翻譯回上下文CGContextTranslateCTM(context, -rotatedSize.width/2, -rotatedSize.height/2);
以及將繪圖中的矩形原點設置為rotatedViewBox.frame.origin.x,rotatedViewBox.frame.origin.y。 使用此代碼。
UIView *rotatedViewBox = [[UIView alloc] initWithFrame:CGRectMake(0,0,image.size.width, image.size.height)];
CGAffineTransform t = CGAffineTransformMakeRotation(0.3);
rotatedViewBox.transform = t;
CGSize rotatedSize = rotatedViewBox.frame.size;
UIGraphicsBeginImageContext(rotatedSize);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, rotatedSize.width/2, rotatedSize.height/2);
CGContextRotateCTM (context, 0.3);
CGContextScaleCTM(context, 1, -1);
CGContextTranslateCTM(context, -rotatedSize.width/2, -rotatedSize.height/2);
CGContextDrawImage(context, CGRectMake(-rotatedViewBox.frame.origin.x, -rotatedViewBox.frame.origin.y, image.size.width, image.size.height), [image CGImage]);
UIImage *nn = UIGraphicsGetImageFromCurrentImageContext();
NSLog(@"size is %f, %f",nn.size.width,nn.size.height);
UIGraphicsEndImageContext();
UIImageWriteToSavedPhotosAlbum(nn, nil,nil,nil);
Cameron Lowell Palmer的Swift 3版本回答:
func createMatchingBackingDataWithImage(imageRef: CGImage?, orienation: UIImageOrientation) -> CGImage? {
var orientedImage: CGImage?
if let imageRef = imageRef {
let originalWidth = imageRef.width
let originalHeight = imageRef.height
let bitsPerComponent = imageRef.bitsPerComponent
let bytesPerRow = imageRef.bytesPerRow
let colorSpace = imageRef.colorSpace
let bitmapInfo = imageRef.bitmapInfo
var degreesToRotate: Double
var swapWidthHeight: Bool
var mirrored: Bool
switch orienation {
case .up:
degreesToRotate = 0.0
swapWidthHeight = false
mirrored = false
break
case .upMirrored:
degreesToRotate = 0.0
swapWidthHeight = false
mirrored = true
break
case .right:
degreesToRotate = 90.0
swapWidthHeight = true
mirrored = false
break
case .rightMirrored:
degreesToRotate = 90.0
swapWidthHeight = true
mirrored = true
break
case .down:
degreesToRotate = 180.0
swapWidthHeight = false
mirrored = false
break
case .downMirrored:
degreesToRotate = 180.0
swapWidthHeight = false
mirrored = true
break
case .left:
degreesToRotate = -90.0
swapWidthHeight = true
mirrored = false
break
case .leftMirrored:
degreesToRotate = -90.0
swapWidthHeight = true
mirrored = true
break
}
let radians = degreesToRotate * Double.pi / 180
var width: Int
var height: Int
if swapWidthHeight {
width = originalHeight
height = originalWidth
} else {
width = originalWidth
height = originalHeight
}
if let contextRef = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace!, bitmapInfo: bitmapInfo.rawValue) {
contextRef.translateBy(x: CGFloat(width) / 2.0, y: CGFloat(height) / 2.0)
if mirrored {
contextRef.scaleBy(x: -1.0, y: 1.0)
}
contextRef.rotate(by: CGFloat(radians))
if swapWidthHeight {
contextRef.translateBy(x: -CGFloat(height) / 2.0, y: -CGFloat(width) / 2.0)
} else {
contextRef.translateBy(x: -CGFloat(width) / 2.0, y: -CGFloat(height) / 2.0)
}
contextRef.draw(imageRef, in: CGRect(x: 0, y: 0, width: originalWidth, height: originalHeight))
orientedImage = contextRef.makeImage()
}
}
return orientedImage
}
用法:
let newCgImage = createMatchingBackingDataWithImage(imageRef: cgImageRef, orienation: .left)
這只是對先前答案的重構。
func correctImageOrientation(cgImage: CGImage?, orienation: UIImage.Orientation) -> CGImage? {
guard let cgImage = cgImage else { return nil }
var orientedImage: CGImage?
let originalWidth = cgImage.width
let originalHeight = cgImage.height
let bitsPerComponent = cgImage.bitsPerComponent
let bytesPerRow = cgImage.bytesPerRow
let bitmapInfo = cgImage.bitmapInfo
guard let colorSpace = cgImage.colorSpace else { return nil }
let degreesToRotate = orienation.getDegree()
let mirrored = orienation.isMirror()
var width = originalWidth
var height = originalHeight
let radians = degreesToRotate * Double.pi / 180.0
let swapWidthHeight = Int(degreesToRotate / 90) % 2 != 0
if swapWidthHeight {
swap(&width, &height)
}
let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)
context?.translateBy(x: CGFloat(width) / 2.0, y: CGFloat(height) / 2.0)
if mirrored {
context?.scaleBy(x: -1.0, y: 1.0)
}
context?.rotate(by: CGFloat(radians))
if swapWidthHeight {
swap(&width, &height)
}
context?.translateBy(x: -CGFloat(width) / 2.0, y: -CGFloat(height) / 2.0)
context?.draw(cgImage, in: CGRect(x: 0.0, y: 0.0, width: CGFloat(originalWidth), height: CGFloat(originalHeight)))
orientedImage = context?.makeImage()
return orientedImage
}
extension UIImage.Orientation {
func getDegree() -> Double {
switch self {
case .up, .upMirrored:
return 0.0
case .right, .rightMirrored:
return 90.0
case .down, .downMirrored:
return 180.0
case .left, .leftMirrored:
return -90.0
default:
return 0
}
}
func isMirror() -> Bool {
switch self {
case .up, .right, .down, .left:
return false
case .leftMirrored, .upMirrored, .downMirrored, .rightMirrored:
return true
default:
return false
}
}
}
所有這些答案的問題在於它們具有 UIKit 依賴項。 我正在開發針對 iOS 和 macOS 的 SwiftUI 通用應用程序。 Cameron Lowell Palmer 的回答有一些修改:
bytesPerRow 計算需要在高度和寬度交換后進行,否則 CGImageContext 抱怨 bytesPerRow 不正確
將 UIImageOrientation 更改為 CGImagePropertyOrientation,並交換左右旋轉的符號
這是我的版本,適用於 iOS 和 macOS:
extension CGImage {
func rotating(to orientation: CGImagePropertyOrientation) -> CGImage? {
var orientedImage: CGImage?
let originalWidth = self.width
let originalHeight = self.height
let bitsPerComponent = self.bitsPerComponent
let bitmapInfo = self.bitmapInfo
guard let colorSpace = self.colorSpace else {
return nil
}
var degreesToRotate: Double
var swapWidthHeight: Bool
var mirrored: Bool
switch orientation {
case .up:
degreesToRotate = 0.0
swapWidthHeight = false
mirrored = false
break
case .upMirrored:
degreesToRotate = 0.0
swapWidthHeight = false
mirrored = true
break
case .right:
degreesToRotate = -90.0
swapWidthHeight = true
mirrored = false
break
case .rightMirrored:
degreesToRotate = -90.0
swapWidthHeight = true
mirrored = true
break
case .down:
degreesToRotate = 180.0
swapWidthHeight = false
mirrored = false
break
case .downMirrored:
degreesToRotate = 180.0
swapWidthHeight = false
mirrored = true
break
case .left:
degreesToRotate = 90.0
swapWidthHeight = true
mirrored = false
break
case .leftMirrored:
degreesToRotate = 90.0
swapWidthHeight = true
mirrored = true
break
}
let radians = degreesToRotate * Double.pi / 180.0
var width: Int
var height: Int
if swapWidthHeight {
width = originalHeight
height = originalWidth
} else {
width = originalWidth
height = originalHeight
}
let bytesPerRow = (width * bitsPerPixel) / 8
let contextRef = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)
contextRef?.translateBy(x: CGFloat(width) / 2.0, y: CGFloat(height) / 2.0)
if mirrored {
contextRef?.scaleBy(x: -1.0, y: 1.0)
}
contextRef?.rotate(by: CGFloat(radians))
if swapWidthHeight {
contextRef?.translateBy(x: -CGFloat(height) / 2.0, y: -CGFloat(width) / 2.0)
} else {
contextRef?.translateBy(x: -CGFloat(width) / 2.0, y: -CGFloat(height) / 2.0)
}
contextRef?.draw(self, in: CGRect(x: 0.0, y: 0.0, width: CGFloat(originalWidth), height: CGFloat(originalHeight)))
orientedImage = contextRef?.makeImage()
return orientedImage
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.