Files
SteUP_Dotnet/SteUp.Maui/Core/Services/ImageOrientation.cs
T
MarcoE 3a9174f6fc Raddrizza le foto applicando l'orientamento EXIF
La fotocamera di sistema non ruota lo scatto: salva i pixel come li legge
il sensore e annota la rotazione in EXIF. SkiaSharp (SKBitmap.Decode)
ignora quel tag, quindi le foto scattate ruotate finivano storte nelle
miniature e venivano inviate cosi' al server.

- ImageOrientation: applica l'orientamento ai pixel e ricodifica senza tag
  residui, mantenendo il formato di partenza. Immagine gia' dritta: nessuna
  ricodifica, per non perdere qualita' inutilmente. In caso di errore
  restituisce i byte originali, meglio una foto storta che nessuna foto.
- UtilityFile.ConvertToDto: normalizza li', unico punto di passaggio di
  fotocamera e galleria, cosi' miniature, salvataggio locale e upload
  lavorano su pixel gia' dritti. Corretto anche DimensionBytes.
- ImageThumb: applica l'orientamento anche in fase di miniatura, per
  raddrizzare le foto gia' presenti sul dispositivo.

Nessun ritratto forzato: costringere una foto orizzontale in verticale
vorrebbe dire ruotare il contenuto o ritagliarlo, e queste foto sono prove
di ispezione.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 10:32:41 +02:00

107 lines
4.0 KiB
C#

using SkiaSharp;
namespace SteUp.Maui.Core.Services;
/// <summary>
/// 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 (<see cref="SKBitmap.Decode(SKCodec)"/>)
/// ignora quel tag, quindi senza questo passaggio le foto scattate ruotate finiscono
/// storte nelle miniature e sul server.
/// </summary>
public static class ImageOrientation
{
/// <summary>
/// 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.
/// </summary>
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;
}
}
/// <summary>
/// Versione dritta di una bitmap già decodificata. Restituisce sempre una nuova
/// bitmap, anche quando non serve ruotare, così chi chiama ne possiede una sola.
/// </summary>
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;
}
}