Se siete degli sviluppatori di app e/o vi capita di lavorare spesso con immagini che contengono fotografie scattate da cellulari e/o tablet conoscerete senz'altro il classico problema delle foto "orientate male", ovvero scattate con il dispositivo girato in una posizione particolare: la particolarità di queste foto è che tendono a visualizzarsi correttamente sul telefono e/o tablet, ma non sulla maggior parte degli applicativi e/o software desktop dove appaiono, per l'appunto, girate di 90, 180 o 270 gradi rispetto alla loro posizione ideale.
Il problema
Il motivo è semplice: al momento di scattare quelle foto, il dispositivo registra la posizione dell'accelerometro all'interno di un tag Exif apposito, che viene poi letto e utilizzato dalle app di visualizzazione per "correggere" in tempo reale l'orientamento dell'immagine. Questa operazione non viene però effettuata dalla maggior parte degli applicativi, software e componenti per PC, i quali si limitano a mostrare l'immagine così come è stata scattata senza effettuare nessuna correzione.
Perché, direte voi? Il motivo è piuttosto semplice: la quasi totalità dei software di elaborazione immagine per PC desktop, a differenza delle app per smartphone, è pensato per immagini e non (solo) per fotografie: i tag Exif, viceversa, sono metadati appositamente pensati per fotocamere digitali. Inoltre, la maggior parte dei suddetti software non è (ancora) pensato per avere a che fare con immagini provenienti da dispositivi dotati di accelerometro, quindi non controllano la presenza dei suddetti tag.
Situazione analoga si verifica anche per i componenti e le librerie di elaborazione immagine, comprese le soluzioni native come GDI+ e DirectX. Per fare un esempio, ogni volta che andremo ad importare una fotografia "diversamente orientata" in C# nel seguente modo, magari per elaborarla o ridimensionarla:
1 |
var bmp = new Bitmap("/path/to/image.jpg"); |
andremo di fatto a mantenere l'orientamento errato.
La soluzione
Fortunatamente, la soluzione a questo problema è piuttosto semplice: è sufficiente dotarsi di un piccola funzione che vada a controllare il tag Exif utilizzato per registrare l'orientamento del dispositivo al momento di scattare la foto e correggere quest'ultima di conseguenza. Il codice necessario si riassume nella seguente helper class da aggiungere al nostro progetto:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
using System; using System.ComponentModel; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.IO; using System.Linq; using Ryadel.Components.IO; using Ryadel.Components.Util; using System.Runtime.InteropServices; using Ryadel.Components.Media.ImageManipulation; using System.Drawing.Text; namespace Ryadel.Components.Media { /// <summary> /// Helper class for manipulating images. /// </summary> public static class ImageHelper { /// <summary> /// Rotate the given image file according to Exif Orientation data /// </summary> /// <param name="sourceFilePath">path of source file</param> /// <param name="targetFilePath">path of target file</param> /// <param name="targetFormat">target format</param> /// <param name="updateExifData">set it to TRUE to update image Exif data after rotation (default is TRUE)</param> /// <returns>The RotateFlipType value corresponding to the applied rotation. If no rotation occurred, RotateFlipType.RotateNoneFlipNone will be returned.</returns> public static RotateFlipType RotateImageByExifOrientationData(string sourceFilePath, string targetFilePath, ImageFormat targetFormat, bool updateExifData = true) { // Rotate the image according to EXIF data var bmp = new Bitmap(sourceFilePath); RotateFlipType fType = RotateImageByExifOrientationData(bmp, updateExifData); if (fType != RotateFlipType.RotateNoneFlipNone) { bmp.Save(targetFilePath, targetFormat); } return fType; } /// <summary> /// Rotate the given bitmap according to Exif Orientation data /// </summary> /// <param name="img">source image</param> /// <param name="updateExifData">set it to TRUE to update image Exif data after rotation (default is TRUE)</param> /// <returns>The RotateFlipType value corresponding to the applied rotation. If no rotation occurred, RotateFlipType.RotateNoneFlipNone will be returned.</returns> public static RotateFlipType RotateImageByExifOrientationData(Image img, bool updateExifData = true) { int orientationId = 0x0112; var fType = RotateFlipType.RotateNoneFlipNone; if (img.PropertyIdList.Contains(orientationId)) { var pItem = img.GetPropertyItem(orientationId); fType = GetRotateFlipTypeByExifOrientationData(pItem.Value[0]); if (fType != RotateFlipType.RotateNoneFlipNone) { img.RotateFlip(fType); if (updateExifData) img.RemovePropertyItem(orientationId); // Remove Exif orientation tag } } return fType; } /// <summary> /// Return the proper System.Drawing.RotateFlipType according to given orientation EXIF metadata /// </summary> /// <param name="orientation">Exif "Orientation"</param> /// <returns>the corresponding System.Drawing.RotateFlipType enum value</returns> public static RotateFlipType GetRotateFlipTypeByExifOrientationData(int orientation) { switch (orientation) { case 1: default: return RotateFlipType.RotateNoneFlipNone; case 2: return RotateFlipType.RotateNoneFlipX; case 3: return RotateFlipType.Rotate180FlipNone; case 4: return RotateFlipType.Rotate180FlipX; case 5: return RotateFlipType.Rotate90FlipX; case 6: return RotateFlipType.Rotate90FlipNone; case 7: return RotateFlipType.Rotate270FlipX; case 8: return RotateFlipType.Rotate270FlipNone; } } } } |
Il metodo include due diversi overload: il primo è una scorciatoia che consente di lavorare direttamente con i path, il secondo dà per scontato che abbiamo già acquisito l'immagine in un oggetto di tipo System.Drawing.Image.
Potete notare come, nel caso in cui si renda necessario intervenire sull'orientamento dell'immagine, il metodo che abbiamo proposto dia anche la possibilità di rimuovere il valore del tag Exif relativo all'orientamento: è infatti del tutto evidente come, ruotando l'immagine in conseguenza di tale informazione, quest'ultima diventi inutile e possa quindi essere eliminata.
Per il momento è tutto: felice orientamento!