97 votes

L'orientation des images dans iOS a un comportement étrange

Ces dernières semaines, j'ai travaillé avec des images en Objective-C et j'ai remarqué un certain nombre de comportements étranges. Tout d'abord, comme beaucoup d'autres personnes, j'ai eu ce problème où les images prises avec l'appareil photo (ou prises avec l'appareil photo de quelqu'un d'autre et envoyées par MMS) sont tournées de 90 degrés. Je n'étais pas sûr de la raison pour laquelle cela se produisait (d'où le fait que je n'ai pas pu m'en rendre compte). ma question ) mais j'ai pu trouver une solution bon marché.

Ma question cette fois-ci est pourquoi cela se produit-il ? ? Pourquoi Apple fait-elle tourner les images ? Lorsque je prends une photo avec mon appareil photo à l'endroit, à moins que je n'exécute le code mentionné ci-dessus, lorsque je sauvegarde la photo, elle est enregistrée avec une rotation. Ma solution de contournement fonctionnait bien jusqu'à il y a quelques jours.

Mon application modifie les pixels individuels d'une image, en particulier le canal alpha d'un PNG (toute conversion JPEG est donc exclue pour mon scénario). Il y a quelques jours, j'ai remarqué que même si l'image s'affiche correctement dans mon application grâce à mon code de contournement, lorsque mon algorithme modifie les pixels individuels de l'image, il pense que l'image est tournée. Ainsi, au lieu de modifier les pixels en haut de l'image, il modifie les pixels sur le côté de l'image (car il pense qu'elle doit être tournée) ! Je n'arrive pas à trouver comment faire pivoter l'image en mémoire - idéalement, je préférerais simplement effacer cette image de la mémoire. imageOrientation drapeau tous ensemble.

Il y a autre chose qui m'a aussi dérouté... Quand je prends la photo, le imageOrientation Mon code de contournement est suffisamment intelligent pour s'en rendre compte et l'inverser afin que l'utilisateur ne le remarque jamais. De plus, mon code pour enregistrer l'image dans la bibliothèque s'en rend compte et l'inverse, puis l'enregistre pour qu'il apparaisse correctement dans le rouleau de l'appareil photo.

Ce code ressemble à ceci :

NSData* pngdata = UIImagePNGRepresentation (self.workingImage); //PNG wrap 
UIImage* img = [self rotateImageAppropriately:[UIImage imageWithData:pngdata]];   
UIImageWriteToSavedPhotosAlbum(img, nil, nil, nil);

Lorsque je charge cette image nouvellement sauvegardée dans mon application, la fonction imageOrientation est 0 - exactement ce que je veux voir, et ma solution de contournement de la rotation n'a même pas besoin d'être exécutée (note : lors du chargement d'images à partir d'Internet par opposition à des images prises avec un appareil photo, la fonction imageOrientation est toujours 0, ce qui donne un comportement parfait). Pour une raison ou une autre, mon code de sauvegarde semble effacer ceci imageOrientation le drapeau. J'espérais simplement voler ce code et l'utiliser pour effacer mon imageOrientation dès que l'utilisateur prend une photo et l'ajoute à l'application, mais cela ne semble pas fonctionner. Est-ce que UIImageWriteToSavedPhotosAlbum faire quelque chose de spécial avec imageOrientation ?

Est-ce que la meilleure solution à ce problème serait de simplement souffler imageOrientation dès que l'utilisateur a fini de prendre une photo. Je suppose qu'Apple a fait ce comportement de rotation pour une raison, n'est-ce pas ? Quelques personnes ont suggéré que c'était un défaut d'Apple.

(... si vous n'êtes pas encore perdu...) Note2 : Lorsque je prends une photo horizontale, tout semble fonctionner parfaitement, tout comme les photos prises sur Internet)

EDITAR:

Voici à quoi ressemblent certaines des images et des scénarios. D'après les commentaires reçus jusqu'à présent, il semble que ce comportement étrange soit plus qu'un simple comportement de l'iPhone, ce qui est une bonne chose.

Voici une image de la photo que j'ai prise avec mon téléphone (notez la bonne orientation), elle apparaît exactement comme elle était sur mon téléphone lorsque j'ai pris la photo :

Actual Photo taken on iPhone

Voici à quoi ressemble l'image dans Gmail après que je me la sois envoyée par courriel (on dirait que Gmail la traite correctement) :

Photo as it appears in Gmail

Voici à quoi ressemble l'image en tant que vignette dans Windows (on dirait qu'elle n'est pas traitée correctement) :

Windows Thumbnail

Et voici à quoi ressemble l'image réelle lorsqu'elle est ouverte avec Windows Photo Viewer (toujours pas traitée correctement) :

Windows Photo Viewer Version

Après tous les commentaires sur cette question, voici ce que je pense... L'iPhone prend une image, et dit "pour l'afficher correctement, il faut la faire pivoter de 90 degrés". Cette information serait dans les données EXIF. (Je ne sais pas pourquoi il faut la faire pivoter de 90 degrés, plutôt que de la mettre à la verticale par défaut). À partir de là, Gmail est suffisamment intelligent pour lire et analyser ces données EXIF, et les afficher correctement. Windows, en revanche, n'est pas assez intelligent pour lire les données EXIF et affiche donc l'image. incorrectement . Mes hypothèses sont-elles correctes ?

0 votes

Intéressant... si c'est vrai, pourquoi les images prises sont-elles réglées sur un angle droit ? Je sais que ce n'est pas un défaut de l'iPhone, car j'ai remarqué un comportement similaire lorsqu'un ami (avec un BlackBerry) prend une photo et me l'envoie par MMS.

0 votes

Vous voyez que votre appareil photo écrit des métadonnées pour le faire pivoter à 90 degrés, certains OS lisent ces métadonnées et d'autres non.

0 votes

Très bonne lecture (voir la réponse de @Hadley ci-dessous - elle était auparavant dans un commentaire). Donc mon application doit clairement être assez intelligente pour gérer les images qui contiennent des données EXIF, ainsi que les images qui n'en contiennent pas... Je ne comprends toujours pas pourquoi lorsque je prends une photo à l'endroit, le drapeau d'orientation dit qu'elle doit être tournée de 90 degrés ?

54voto

Dilip Rajkumar Points 3162

J'ai eu le même problème lorsque je reçois l'image de la caméra, j'ai mis le code suivant pour le résoudre Ajouté la méthode scaleAndRotateImage d'ici

- (void) imagePickerController:(UIImagePickerController *)thePicker didFinishPickingMediaWithInfo:(NSDictionary *)imageInfo {
            // Images from the camera are always in landscape, so rotate
                    UIImage *image = [self scaleAndRotateImage: [imageInfo objectForKey:UIImagePickerControllerOriginalImage]];
    //then save the image to photo gallery or wherever  
        }

- (UIImage *)scaleAndRotateImage:(UIImage *) image {
    int kMaxResolution = 320;

    CGImageRef imgRef = image.CGImage;

    CGFloat width = CGImageGetWidth(imgRef);
    CGFloat height = CGImageGetHeight(imgRef);

    CGAffineTransform transform = CGAffineTransformIdentity;
    CGRect bounds = CGRectMake(0, 0, width, height);
    if (width > kMaxResolution || height > kMaxResolution) {
        CGFloat ratio = width/height;
        if (ratio > 1) {
            bounds.size.width = kMaxResolution;
            bounds.size.height = bounds.size.width / ratio;
        }
        else {
            bounds.size.height = kMaxResolution;
            bounds.size.width = bounds.size.height * ratio;
        }
    }

    CGFloat scaleRatio = bounds.size.width / width;
    CGSize imageSize = CGSizeMake(CGImageGetWidth(imgRef), CGImageGetHeight(imgRef));
    CGFloat boundHeight;
    UIImageOrientation orient = image.imageOrientation;
    switch(orient) {

        case UIImageOrientationUp: //EXIF = 1
            transform = CGAffineTransformIdentity;
            break;

        case UIImageOrientationUpMirrored: //EXIF = 2
            transform = CGAffineTransformMakeTranslation(imageSize.width, 0.0);
            transform = CGAffineTransformScale(transform, -1.0, 1.0);
            break;

        case UIImageOrientationDown: //EXIF = 3
            transform = CGAffineTransformMakeTranslation(imageSize.width, imageSize.height);
            transform = CGAffineTransformRotate(transform, M_PI);
            break;

        case UIImageOrientationDownMirrored: //EXIF = 4
            transform = CGAffineTransformMakeTranslation(0.0, imageSize.height);
            transform = CGAffineTransformScale(transform, 1.0, -1.0);
            break;

        case UIImageOrientationLeftMirrored: //EXIF = 5
            boundHeight = bounds.size.height;
            bounds.size.height = bounds.size.width;
            bounds.size.width = boundHeight;
            transform = CGAffineTransformMakeTranslation(imageSize.height, imageSize.width);
            transform = CGAffineTransformScale(transform, -1.0, 1.0);
            transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0);
            break;

        case UIImageOrientationLeft: //EXIF = 6
            boundHeight = bounds.size.height;
            bounds.size.height = bounds.size.width;
            bounds.size.width = boundHeight;
            transform = CGAffineTransformMakeTranslation(0.0, imageSize.width);
            transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0);
            break;

        case UIImageOrientationRightMirrored: //EXIF = 7
            boundHeight = bounds.size.height;
            bounds.size.height = bounds.size.width;
            bounds.size.width = boundHeight;
            transform = CGAffineTransformMakeScale(-1.0, 1.0);
            transform = CGAffineTransformRotate(transform, M_PI / 2.0);
            break;

        case UIImageOrientationRight: //EXIF = 8
            boundHeight = bounds.size.height;
            bounds.size.height = bounds.size.width;
            bounds.size.width = boundHeight;
            transform = CGAffineTransformMakeTranslation(imageSize.height, 0.0);
            transform = CGAffineTransformRotate(transform, M_PI / 2.0);
            break;

        default:
            [NSException raise:NSInternalInconsistencyException format:@"Invalid image orientation"];

    }

    UIGraphicsBeginImageContext(bounds.size);

    CGContextRef context = UIGraphicsGetCurrentContext();

    if (orient == UIImageOrientationRight || orient == UIImageOrientationLeft) {
        CGContextScaleCTM(context, -scaleRatio, scaleRatio);
        CGContextTranslateCTM(context, -height, 0);
    }
    else {
        CGContextScaleCTM(context, scaleRatio, -scaleRatio);
        CGContextTranslateCTM(context, 0, -height);
    }

    CGContextConcatCTM(context, transform);

    CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, width, height), imgRef);
    UIImage *imageCopy = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return imageCopy;
}

50voto

HarshIT Points 1309

J'ai fait de la recherche et du développement sur ce sujet et j'ai découvert que chaque fichier image a une propriété de métadonnées. Si les métadonnées spécifient l'orientation de l'image, ce qui est généralement ignoré par les autres OS sauf Mac. La plupart des images prises ont leur propriété de métadonnées réglée sur un angle droit. Mac les affiche donc avec une rotation de 90 degrés. Vous pouvez voir la même image de manière correcte dans le système d'exploitation Windows.

Pour plus de détails, lisez cette réponse http://graphicssoft.about.com/od/digitalphotography/f/sideways-pictures.htm

essayez de lire l'exif de votre image ici http://www.exifviewer.org/ ou http://regex.info/exif.cgi ou http://www.addictivetips.com/internet-tips/view-complete-exif-metadata-information-of-any-jpeg-image-online/

25voto

Roy Points 231

Ma question, cette fois, est la suivante : pourquoi cela se produit-il ? Pourquoi Apple fait-elle tourner les images ?

La réponse à cette question est très simple. Apple ne fait PAS tourner l'image. C'est là que réside la confusion.

La caméra CCD ne tourne pas, elle prend donc toujours la photo en mode paysage.

Apple a fait une chose très intelligente : au lieu de passer tout le temps à faire pivoter l'image - en brassant des mégaoctets de données - il suffit de l'étiqueter en indiquant COMMENT la photo a été prise.

OpenGL fait des traductions très facilement, de sorte que les DONNÉES ne sont jamais mélangées, mais seulement COMMENT ELLES SONT DESSINÉES.

D'où les métadonnées d'orientation.

Cela devient un problème si vous voulez recadrer, redimensionner etc. - mais une fois que vous savez ce qui se passe, il vous suffit de définir votre matrice et tout se passe bien.

18voto

thattyson Points 696

Copier/coller rapide de la traduction Swift de Dilip. excellente réponse .

import Darwin

class func rotateCameraImageToProperOrientation(imageSource : UIImage, maxResolution : CGFloat) -> UIImage {

    let imgRef = imageSource.CGImage;

    let width = CGFloat(CGImageGetWidth(imgRef));
    let height = CGFloat(CGImageGetHeight(imgRef));

    var bounds = CGRectMake(0, 0, width, height)

    var scaleRatio : CGFloat = 1
    if (width > maxResolution || height > maxResolution) {

        scaleRatio = min(maxResolution / bounds.size.width, maxResolution / bounds.size.height)
        bounds.size.height = bounds.size.height * scaleRatio
        bounds.size.width = bounds.size.width * scaleRatio
    }

    var transform = CGAffineTransformIdentity
    let orient = imageSource.imageOrientation
    let imageSize = CGSizeMake(CGFloat(CGImageGetWidth(imgRef)), CGFloat(CGImageGetHeight(imgRef)))

    switch(imageSource.imageOrientation) {
    case .Up :
        transform = CGAffineTransformIdentity

    case .UpMirrored :
        transform = CGAffineTransformMakeTranslation(imageSize.width, 0.0);
        transform = CGAffineTransformScale(transform, -1.0, 1.0);

    case .Down :
        transform = CGAffineTransformMakeTranslation(imageSize.width, imageSize.height);
        transform = CGAffineTransformRotate(transform, CGFloat(M_PI));

    case .DownMirrored :
        transform = CGAffineTransformMakeTranslation(0.0, imageSize.height);
        transform = CGAffineTransformScale(transform, 1.0, -1.0);

    case .Left :
        let storedHeight = bounds.size.height
        bounds.size.height = bounds.size.width;
        bounds.size.width = storedHeight;
        transform = CGAffineTransformMakeTranslation(0.0, imageSize.width);
        transform = CGAffineTransformRotate(transform, 3.0 * CGFloat(M_PI) / 2.0);

    case .LeftMirrored :
        let storedHeight = bounds.size.height
        bounds.size.height = bounds.size.width;
        bounds.size.width = storedHeight;
        transform = CGAffineTransformMakeTranslation(imageSize.height, imageSize.width);
        transform = CGAffineTransformScale(transform, -1.0, 1.0);
        transform = CGAffineTransformRotate(transform, 3.0 * CGFloat(M_PI) / 2.0);

    case .Right :
        let storedHeight = bounds.size.height
        bounds.size.height = bounds.size.width;
        bounds.size.width = storedHeight;
        transform = CGAffineTransformMakeTranslation(imageSize.height, 0.0);
        transform = CGAffineTransformRotate(transform, CGFloat(M_PI) / 2.0);

    case .RightMirrored :
        let storedHeight = bounds.size.height
        bounds.size.height = bounds.size.width;
        bounds.size.width = storedHeight;
        transform = CGAffineTransformMakeScale(-1.0, 1.0);
        transform = CGAffineTransformRotate(transform, CGFloat(M_PI) / 2.0);

    default : ()
    }

    UIGraphicsBeginImageContext(bounds.size)
    let context = UIGraphicsGetCurrentContext()

    if orient == .Right || orient == .Left {
        CGContextScaleCTM(context, -scaleRatio, scaleRatio);
        CGContextTranslateCTM(context, -height, 0);
    } else {
        CGContextScaleCTM(context, scaleRatio, -scaleRatio);
        CGContextTranslateCTM(context, 0, -height);
    }

    CGContextConcatCTM(context, transform);
    CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, width, height), imgRef);

    let imageCopy = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return imageCopy;
}

17voto

Dávid Tímár Points 561

Version Swift 4 avec contrôles de sécurité de La réponse de Dilip .

public static func rotateCameraImageToProperOrientation(imageSource : UIImage, maxResolution : CGFloat = 320) -> UIImage? {

    guard let imgRef = imageSource.cgImage else {
        return nil
    }

    let width = CGFloat(imgRef.width)
    let height = CGFloat(imgRef.height)

    var bounds = CGRect(x: 0, y: 0, width: width, height: height)

    var scaleRatio : CGFloat = 1
    if (width > maxResolution || height > maxResolution) {

        scaleRatio = min(maxResolution / bounds.size.width, maxResolution / bounds.size.height)
        bounds.size.height = bounds.size.height * scaleRatio
        bounds.size.width = bounds.size.width * scaleRatio
    }

    var transform = CGAffineTransform.identity
    let orient = imageSource.imageOrientation
    let imageSize = CGSize(width: CGFloat(imgRef.width), height: CGFloat(imgRef.height))

    switch(imageSource.imageOrientation) {
    case .up:
        transform = .identity
    case .upMirrored:
        transform = CGAffineTransform
            .init(translationX: imageSize.width, y: 0)
            .scaledBy(x: -1.0, y: 1.0)
    case .down:
        transform = CGAffineTransform
            .init(translationX: imageSize.width, y: imageSize.height)
            .rotated(by: CGFloat.pi)
    case .downMirrored:
        transform = CGAffineTransform
            .init(translationX: 0, y: imageSize.height)
            .scaledBy(x: 1.0, y: -1.0)
    case .left:
        let storedHeight = bounds.size.height
        bounds.size.height = bounds.size.width;
        bounds.size.width = storedHeight;
        transform = CGAffineTransform
            .init(translationX: 0, y: imageSize.width)
            .rotated(by: 3.0 * CGFloat.pi / 2.0)
    case .leftMirrored:
        let storedHeight = bounds.size.height
        bounds.size.height = bounds.size.width;
        bounds.size.width = storedHeight;
        transform = CGAffineTransform
            .init(translationX: imageSize.height, y: imageSize.width)
            .scaledBy(x: -1.0, y: 1.0)
            .rotated(by: 3.0 * CGFloat.pi / 2.0)
    case .right :
        let storedHeight = bounds.size.height
        bounds.size.height = bounds.size.width;
        bounds.size.width = storedHeight;
        transform = CGAffineTransform
            .init(translationX: imageSize.height, y: 0)
            .rotated(by: CGFloat.pi / 2.0)
    case .rightMirrored:
        let storedHeight = bounds.size.height
        bounds.size.height = bounds.size.width;
        bounds.size.width = storedHeight;
        transform = CGAffineTransform
            .init(scaleX: -1.0, y: 1.0)
            .rotated(by: CGFloat.pi / 2.0)
    }

    UIGraphicsBeginImageContext(bounds.size)
    if let context = UIGraphicsGetCurrentContext() {
        if orient == .right || orient == .left {
            context.scaleBy(x: -scaleRatio, y: scaleRatio)
            context.translateBy(x: -height, y: 0)
        } else {
            context.scaleBy(x: scaleRatio, y: -scaleRatio)
            context.translateBy(x: 0, y: -height)
        }

        context.concatenate(transform)
        context.draw(imgRef, in: CGRect(x: 0, y: 0, width: width, height: height))
    }

    let imageCopy = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    return imageCopy
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X