Rotación de una imagen CG

Tengo una UIImage que obtengo de un UIImagePickerController. Cuando recibo la imagen del - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info método lo puse directamente en un UIImageView para obtener una vista previa y le doy al usuario la opción de guardar o descartar la imagen.

La pantalla de vista previa

Cuando el usuario selecciona la opción de guardar, la aplicación obtiene los datos png de la imagen y los guarda en su directorio de documentos. Pero lo que sucede bajo una de las 2 orientaciones posibles del dispositivo (solo paisaje) es que la imagen se guarda al revés (más específicamente, gira 180 grados de lo que debería ser). Entonces, cuando cargo la imagen en la galería, aparece al revés.

(Ver la imagen inferior izquierda en la galería)

Ver la imagen inferior izquierda

He resuelto que los datos de imagen sin procesar en la UIImage del UIImagePickerController no se giran, sino que la orientación se almacena como una propiedad en el objeto y solo se aplica cuando se muestra. Así que estoy tratando de rotar la CGImage asociada con la UIImage usando un código que encontré en línea. Pero parece no tener absolutamente ningún efecto en la imagen. El código que estoy usando es el siguiente:

- (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();
}

Me gustaría saber por qué este código no funciona, ya que cada pieza de código que encuentro en línea para hacer esta rotación de imágenes parece no funcionar.

preguntado el 11 de mayo de 12 a las 04:05

6 Respuestas

-(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;

}

usa este.. funciona perfecto! Solo asegúrese de usar esta función cuando elija imágenes de UIImagePicker:

ejemplo:

UIImage *image;
image = [self rotate:image andOrientation:image.imageOrientation];

Asegúrese de usar esta función en el lugar donde selecciona fotos del directorio de documentos.

contestado el 12 de mayo de 12 a las 09:05

buena respuesta... lo único que cambiaría es que "return img;"le llaman antes"[img release];" (lo que significa que la llamada de liberación en realidad nunca llega). Además, esto supone que la persona no está usando ARC. - Michael Dautermann

¿Se sabe por qué es necesaria la rotación? - las pruebas

Rotar y reflejar datos de respaldo de UIImage CGImage - Swift

Hace poco quise corregir un UIImage respaldado por un CGImage para que coincida con la orientación deseada en lugar de depender de las API para respetar el parámetro 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
}

Respondido el 26 de Septiembre de 17 a las 19:09

"estás rotando el contexto (y su sistema de coordenadas) y luego dibujando la imagen en él". Por eso, podrían haber llamado caballete a un contexto, ¿no? Me pregunto por qué no lo hicieron. Es más fácil entender qué es un "caballete" (después de todo, la mayoría de la gente ha usado uno) que entender qué es un "contexto". - montana rebabas

¿Cuál es la diferencia entre su solución y la de Sueño en código? Está teniendo en cuenta más casos (por ejemplo, imágenes reflejadas). ¿Pero qué más? Algunos ángulos parecen ser diferentes y también los métodos utilizados (CGContextRotateCTM vs rotate). También estás haciendo un translateBy, que la otra solución no tiene en cuenta... - las pruebas

También cuál es la diferencia con este Esencia? - las pruebas

Tengo tu problema. Tienes que volver a traducir el contexto usando CGContextTranslateCTM(context, -rotatedSize.width/2, -rotatedSize.height/2); así como establecer el origen de rect en el dibujo en rotatedViewBox.frame.origin.x,rotatedViewBox.frame.origin.y. utiliza este código.

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);

Respondido el 11 de Septiembre de 12 a las 12:09

La Swift 3 versión para el Respuesta de cameron lowell palmer:

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
}

Uso:

let newCgImage = createMatchingBackingDataWithImage(imageRef: cgImageRef, orienation: .left)

contestado el 23 de mayo de 17 a las 13:05

Esto es solo una refactorización de la respuesta anterior.

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
        }
    }
}

contestado el 14 de mayo de 20 a las 09:05

El problema con todas estas respuestas es que tienen dependencias de UIKit. Estoy trabajando en una aplicación SwiftUI Universal para iOS y macOS. Él Respuesta de cameron lowell palmer funciona con algunas modificaciones:

  1. El cálculo de bytesPerRow debe realizarse después de el intercambio de alto y ancho, o el CGImageContext se queja de bytesPerRow incorrectos

  2. cambie UIImageOrientation a CGImagePropertyOrientation e intercambie los signos para rotaciones izquierda y derecha

Aquí está mi versión, adecuada tanto para iOS como para 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
}

}

Respondido el 18 de junio de 21 a las 21:06

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.