diff --git a/SteUp.Maui/Core/Services/ImageOrientation.cs b/SteUp.Maui/Core/Services/ImageOrientation.cs
new file mode 100644
index 0000000..5f816ab
--- /dev/null
+++ b/SteUp.Maui/Core/Services/ImageOrientation.cs
@@ -0,0 +1,106 @@
+using SkiaSharp;
+
+namespace SteUp.Maui.Core.Services;
+
+///
+/// Raddrizza le immagini applicando l'orientamento EXIF ai pixel.
+/// La fotocamera di sistema non ruota lo scatto: salva i pixel come li ha letti il
+/// sensore e annota la rotazione in EXIF. SkiaSharp ()
+/// ignora quel tag, quindi senza questo passaggio le foto scattate ruotate finiscono
+/// storte nelle miniature e sul server.
+///
+public static class ImageOrientation
+{
+ ///
+ /// Restituisce i byte dell'immagine con i pixel già nel verso giusto e senza tag
+ /// di orientamento residui. In caso di formato non gestito o errore restituisce
+ /// l'originale: meglio una foto storta che perdere lo scatto del rilevatore.
+ ///
+ public static byte[] Normalize(byte[] bytes, int quality = 92)
+ {
+ try
+ {
+ using var codec = SKCodec.Create(new SKMemoryStream(bytes));
+ if (codec is null) return bytes;
+
+ var origin = codec.EncodedOrigin;
+
+ // Già dritta: non ricodifichiamo, così non si perde qualità inutilmente.
+ if (origin == SKEncodedOrigin.TopLeft) return bytes;
+
+ using var decoded = SKBitmap.Decode(codec);
+ if (decoded is null) return bytes;
+
+ using var upright = ApplyOrigin(decoded, origin);
+ using var image = SKImage.FromBitmap(upright);
+
+ // Manteniamo il formato di partenza: il nome file conserva la sua estensione.
+ var format = codec.EncodedFormat == SKEncodedImageFormat.Png
+ ? SKEncodedImageFormat.Png
+ : SKEncodedImageFormat.Jpeg;
+
+ using var encoded = image.Encode(format, quality);
+ return encoded?.ToArray() ?? bytes;
+ }
+ catch
+ {
+ return bytes;
+ }
+ }
+
+ ///
+ /// Versione dritta di una bitmap già decodificata. Restituisce sempre una nuova
+ /// bitmap, anche quando non serve ruotare, così chi chiama ne possiede una sola.
+ ///
+ public static SKBitmap Upright(SKBitmap src, SKEncodedOrigin origin) =>
+ origin == SKEncodedOrigin.TopLeft ? src.Copy() : ApplyOrigin(src, origin);
+
+ private static SKBitmap ApplyOrigin(SKBitmap src, SKEncodedOrigin origin)
+ {
+ // Gli orientamenti "di lato" scambiano larghezza e altezza.
+ var sideways = origin is SKEncodedOrigin.LeftTop or SKEncodedOrigin.RightTop
+ or SKEncodedOrigin.RightBottom or SKEncodedOrigin.LeftBottom;
+
+ var w = sideways ? src.Height : src.Width;
+ var h = sideways ? src.Width : src.Height;
+
+ var dst = new SKBitmap(w, h, src.ColorType, src.AlphaType);
+ using var canvas = new SKCanvas(dst);
+
+ switch (origin)
+ {
+ case SKEncodedOrigin.TopRight: // specchiata in orizzontale
+ canvas.Translate(w, 0);
+ canvas.Scale(-1, 1);
+ break;
+ case SKEncodedOrigin.BottomRight: // ruotata di 180°
+ canvas.Translate(w, h);
+ canvas.RotateDegrees(180);
+ break;
+ case SKEncodedOrigin.BottomLeft: // specchiata in verticale
+ canvas.Translate(0, h);
+ canvas.Scale(1, -1);
+ break;
+ case SKEncodedOrigin.LeftTop: // trasposta
+ canvas.RotateDegrees(90);
+ canvas.Scale(1, -1);
+ break;
+ case SKEncodedOrigin.RightTop: // ruotata di 90° in senso orario
+ canvas.Translate(w, 0);
+ canvas.RotateDegrees(90);
+ break;
+ case SKEncodedOrigin.RightBottom: // trasversa
+ canvas.Translate(w, h);
+ canvas.RotateDegrees(270);
+ canvas.Scale(1, -1);
+ break;
+ case SKEncodedOrigin.LeftBottom: // ruotata di 90° in senso antiorario
+ canvas.Translate(0, h);
+ canvas.RotateDegrees(-90);
+ break;
+ }
+
+ canvas.DrawBitmap(src, 0, 0);
+ return dst;
+ }
+}
diff --git a/SteUp.Maui/Core/Services/ImageThumb.cs b/SteUp.Maui/Core/Services/ImageThumb.cs
index 2ba4ff0..bc552e9 100644
--- a/SteUp.Maui/Core/Services/ImageThumb.cs
+++ b/SteUp.Maui/Core/Services/ImageThumb.cs
@@ -19,11 +19,15 @@ public static class ImageThumb
throw new InvalidOperationException("Formato immagine non supportato o file corrotto.");
// Decodifica
- var info = codec.Info;
- using var bitmap = SKBitmap.Decode(codec);
- if (bitmap is null)
+ using var decoded = SKBitmap.Decode(codec);
+ if (decoded is null)
throw new InvalidOperationException("Impossibile decodificare l'immagine.");
+ // SKBitmap.Decode non applica l'orientamento EXIF: senza questo passaggio le
+ // miniature delle foto scattate ruotate escono storte (vale anche per gli
+ // scatti già sul dispositivo, che non passano da UtilityFile.ConvertToDto).
+ using var bitmap = ImageOrientation.Upright(decoded, codec.EncodedOrigin);
+
// Calcola resize mantenendo aspect ratio
var w = bitmap.Width;
var h = bitmap.Height;
diff --git a/SteUp.Maui/Core/Utility/UtilityFile.cs b/SteUp.Maui/Core/Utility/UtilityFile.cs
index 83665e7..c4b65d8 100644
--- a/SteUp.Maui/Core/Utility/UtilityFile.cs
+++ b/SteUp.Maui/Core/Utility/UtilityFile.cs
@@ -1,3 +1,4 @@
+using SteUp.Maui.Core.Services;
using SteUp.Shared.Core.Dto;
namespace SteUp.Maui.Core.Utility;
@@ -10,13 +11,20 @@ public static class UtilityFile
using var ms = new MemoryStream();
await stream.CopyToAsync(ms);
+ var bytes = ms.ToArray();
+
+ // Unico punto di passaggio di fotocamera e galleria: raddrizziamo qui, cosi'
+ // miniature, salvataggio locale e upload lavorano tutti su pixel gia' dritti.
+ if (type == AttachedDto.TypeAttached.Image)
+ bytes = ImageOrientation.Normalize(bytes);
+
return new AttachedDto
{
Name = file.FileName,
Path = file.FullPath,
MimeType = file.ContentType,
- DimensionBytes = ms.Length,
- FileBytes = ms.ToArray(),
+ DimensionBytes = bytes.Length,
+ FileBytes = bytes,
Type = type
};
}