From 332f02d17d56d877471ab74a7972fa9e2690e3cc Mon Sep 17 00:00:00 2001 From: Alberto Balbo Date: Fri, 14 Aug 2026 15:53:10 +0200 Subject: [PATCH] Orientamento regolabile, profilo di qualita' e icona dell'applicazione ORIENTAMENTO Il tag Exif dice come raddrizzare l'immagine, ma non basta leggerlo: alcuni decodificatori di sistema lo applicano gia' per conto proprio e altri no, e la stessa installazione di Windows si comporta diversamente a seconda del formato. Applicare la trasformazione due volte ribalta il fotogramma, non applicarla mai lo lascia coricato. In modalita' automatica Titano confronta ora le dimensioni che il decodificatore restituisce con quelle che il file dichiara di contenere: se la trasformazione scambia gli assi ed e' gia' stata applicata, non la riapplica. La sezione Generale mostra cosa ha dedotto e con quale motivo, e permette di imporre una delle sei trasformazioni Exif quando i metadati sono sbagliati. L'anteprima mostra subito l'effetto della scelta. Sui DNG GoPro il rilevamento riporta "specchiata orizzontalmente (dal tag Exif del file)": e' quanto dichiara il file e quanto applicano i lettori conformi alla specifica DNG. Se all'occhio risulta sbagliato, ora si cambia dal menu. QUALITA' PRIMA DELLA VELOCITA' Nuovo profilo di elaborazione con tre livelli, predefinito su Massima. Governa la finezza della griglia del campo vettoriale, i livelli della piramide, le iterazioni per livello, i campioni per pixel della scia e la risoluzione della passata fotometrica. Costa circa tre volte il tempo del profilo Standard e si vede nei bordi dei soggetti in movimento. Non e' stato introdotto alcun uso del disco come memoria d'appoggio: nessuna fase della pipeline supera cio' che la RAM regge, e i file temporanei sono esclusi per scelta architetturale. Se in futuro servira' tenere in vita molti fotogrammi a piena risoluzione, quella e' la porta da cui entrare. ICONA Scie stellari attorno al polo celeste: l'immagine in cui il tempo diventa visibile, e che dice insieme time-lapse e cielo notturno senza ricorrere a orologi o otturatori. Disegnata in-house con GDI+, contenitore ICO scritto a mano con sette livelli. Sotto i venti pixel il disegno si semplifica a due archi piu' spessi: alla dimensione della barra delle applicazioni sette scie sottili diventerebbero una macchia. La verifica del motore sale a 25 controlli: si aggiunge che lo specchiamento richiesto sposti davvero i pixel. Co-Authored-By: Claude Opus 5 --- Titano/Diagnostics/IconGenerator.cs | 274 ++++++++++++++++++++++ Titano/Diagnostics/SelfTest.cs | 19 ++ Titano/Diagnostics/SequenceDiagnostics.cs | 3 + Titano/Imaging/ImageDecoder.cs | 14 ++ Titano/Imaging/OrientationDetector.cs | 62 +++++ Titano/Pipeline/RenderPipeline.cs | 12 +- Titano/Pipeline/TitanoProject.cs | 98 +++++++- Titano/Program.cs | 8 + Titano/README.md | 23 +- Titano/Titano.csproj | 1 + Titano/Titano.ico | Bin 0 -> 142682 bytes Titano/UI/MainForm.cs | 23 +- Titano/UI/PreviewPanel.cs | 9 +- Titano/UI/SettingsPanel.cs | 57 +++++ 14 files changed, 588 insertions(+), 15 deletions(-) create mode 100644 Titano/Diagnostics/IconGenerator.cs create mode 100644 Titano/Imaging/OrientationDetector.cs create mode 100644 Titano/Titano.ico diff --git a/Titano/Diagnostics/IconGenerator.cs b/Titano/Diagnostics/IconGenerator.cs new file mode 100644 index 0000000..2212072 --- /dev/null +++ b/Titano/Diagnostics/IconGenerator.cs @@ -0,0 +1,274 @@ +using System.Buffers.Binary; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; + +namespace Titano.Diagnostics; + +/// +/// Generatore dell'icona dell'applicazione. +/// +/// Il soggetto sono le scie stellari: in una posa lunga le stelle lasciano archi concentrici +/// attorno al polo celeste, ed è l'immagine in cui il tempo diventa visibile. Dice insieme +/// "time-lapse" e "cielo notturno" senza ricorrere a orologi o otturatori. +/// +/// L'icona non è un file d'arte importato: viene disegnata qui e il contenitore ICO è scritto +/// a mano, coerentemente con il resto del progetto. +/// +public static class IconGenerator +{ + private static readonly int[] Sizes = [16, 24, 32, 48, 64, 128, 256]; + + public static void Write(string path) + { + var images = new List<(int Size, byte[] Data, bool Png)>(); + + foreach (int size in Sizes) + { + using var bitmap = Render(size); + // Le dimensioni piccole restano in DIB, la più grande in PNG: è la combinazione + // che ogni versione di Windows sa leggere senza sorprese. + images.Add(size >= 256 + ? (size, EncodePng(bitmap), true) + : (size, EncodeDib(bitmap), false)); + } + + using var stream = new FileStream(path, FileMode.Create, FileAccess.Write); + Span header = stackalloc byte[6]; + BinaryPrimitives.WriteUInt16LittleEndian(header, 0); // riservato + BinaryPrimitives.WriteUInt16LittleEndian(header[2..], 1); // tipo: icona + BinaryPrimitives.WriteUInt16LittleEndian(header[4..], (ushort)images.Count); + stream.Write(header); + + int offset = 6 + 16 * images.Count; + Span entry = stackalloc byte[16]; + + foreach (var (size, data, _) in images) + { + entry.Clear(); + entry[0] = (byte)(size >= 256 ? 0 : size); // 0 significa 256 + entry[1] = (byte)(size >= 256 ? 0 : size); + entry[2] = 0; // colori della tavolozza + entry[3] = 0; // riservato + BinaryPrimitives.WriteUInt16LittleEndian(entry[4..], 1); // piani + BinaryPrimitives.WriteUInt16LittleEndian(entry[6..], 32); // bit per pixel + BinaryPrimitives.WriteUInt32LittleEndian(entry[8..], (uint)data.Length); + BinaryPrimitives.WriteUInt32LittleEndian(entry[12..], (uint)offset); + stream.Write(entry); + offset += data.Length; + } + + foreach (var (_, data, _) in images) stream.Write(data); + } + + // ------------------------------------------------------------------ disegno + + private static Bitmap Render(int size) + { + var bitmap = new Bitmap(size, size, PixelFormat.Format32bppArgb); + using var g = Graphics.FromImage(bitmap); + + g.SmoothingMode = SmoothingMode.AntiAlias; + g.InterpolationMode = InterpolationMode.HighQualityBicubic; + g.PixelOffsetMode = PixelOffsetMode.HighQuality; + g.Clear(Color.Transparent); + + float s = size; + var bounds = new RectangleF(s * 0.015f, s * 0.015f, s * 0.97f, s * 0.97f); + + // Cielo: dal blu notte dell'ora blu al nero dello zenit. + using (var path = RoundedRectangle(bounds, s * 0.22f)) + { + using var sky = new LinearGradientBrush( + new PointF(0, bounds.Top), new PointF(0, bounds.Bottom), + Color.FromArgb(0x0E, 0x16, 0x2C), Color.FromArgb(0x04, 0x06, 0x0D)); + g.FillPath(sky, path); + g.SetClip(path); + } + + var pole = new PointF(s * 0.50f, s * 0.44f); + + // Sotto i venti pixel non c'è spazio per sette archi sottili: diventano una macchia. + // Le dimensioni piccole ricevono quindi meno scie, più spesse e più distanti fra loro. + // È la stessa immagine, detta con meno parole. + bool tiny = size <= 20; + bool small = size <= 36; + + float trailWidth = Math.Max(1.4f, s * (tiny ? 0.10f : small ? 0.072f : 0.042f)); + + Color coolWhite = Color.FromArgb(0xD8, 0xEA, 0xFF); + Color iceBlue = Color.FromArgb(0x8F, 0xC8, 0xFF); + Color warm = Color.FromArgb(0xFF, 0xDA, 0xA8); + + // Archi concentrici: raggio crescente, apertura crescente verso l'esterno, come + // accade davvero — le stelle lontane dal polo percorrono più cielo. + (float Radius, float Start, float Sweep, Color Tint)[] trails = tiny + ? + [ + (0.23f, 165f, 150f, coolWhite), + (0.40f, 335f, 135f, warm), + ] + : small + ? + [ + (0.17f, 190f, 130f, warm), + (0.30f, 10f, 150f, coolWhite), + (0.43f, 150f, 165f, iceBlue), + ] + : + [ + (0.10f, 200f, 120f, Color.FromArgb(0xFF, 0xE6, 0xC8)), + (0.19f, 20f, 140f, Color.FromArgb(0xCF, 0xE4, 0xFF)), + (0.19f, 215f, 95f, Color.FromArgb(0xFF, 0xD3, 0x9C)), + (0.29f, 165f, 160f, Color.FromArgb(0x9C, 0xD4, 0xFF)), + (0.29f, 355f, 85f, Color.FromArgb(0xFF, 0xFF, 0xFF)), + (0.39f, 130f, 175f, Color.FromArgb(0x7F, 0xC4, 0xFF)), + (0.39f, 330f, 100f, Color.FromArgb(0xFF, 0xE1, 0xB4)), + ]; + + foreach (var (radiusFactor, start, sweep, tint) in trails) + { + float radius = s * radiusFactor; + var box = new RectangleF(pole.X - radius, pole.Y - radius, radius * 2, radius * 2); + + // Un tratto continuo tenue sotto, e sopra la sfumatura a segmenti: senza la base + // i segmenti si leggerebbero come una collana di perle invece che come una scia. + using (var basePen = new Pen(Color.FromArgb(26, tint), trailWidth) + { + StartCap = LineCap.Round, + EndCap = LineCap.Round, + }) + { + g.DrawArc(basePen, box, start, sweep); + } + + int steps = Math.Max(24, (int)(sweep / 1.5f)); + for (int i = 0; i < steps; i++) + { + float t = i / (float)(steps - 1); + // La coda sfuma, la testa resta accesa: è così che si legge il verso del moto. + int alpha = (int)(10 + 245 * t * t); + float a0 = start + sweep * i / steps; + float a1 = start + sweep * (i + 1) / steps; + + using var pen = new Pen(Color.FromArgb(alpha, tint), trailWidth) + { + StartCap = LineCap.Round, + EndCap = LineCap.Round, + }; + // I segmenti si sovrappongono: le estremità arrotondate si fondono fra loro. + g.DrawArc(pen, box, a0, a1 - a0 + (sweep / steps) * 0.9f); + } + + // Stella in testa alla scia. + double head = (start + sweep) * Math.PI / 180.0; + var tip = new PointF(pole.X + radius * (float)Math.Cos(head), pole.Y + radius * (float)Math.Sin(head)); + float star = trailWidth * 0.85f; + using var glow = new SolidBrush(Color.FromArgb(235, Color.White)); + g.FillEllipse(glow, tip.X - star, tip.Y - star, star * 2, star * 2); + } + + // Polo celeste: il punto fermo attorno a cui gira tutto. + float core = Math.Max(1f, s * (tiny ? 0.055f : 0.035f)); + float haloFactor = tiny ? 1.7f : 2.4f; + using (var halo = new SolidBrush(Color.FromArgb(tiny ? 50 : 70, 0xBF, 0xDC, 0xFF))) + g.FillEllipse(halo, pole.X - core * haloFactor, pole.Y - core * haloFactor, + core * haloFactor * 2, core * haloFactor * 2); + using (var center = new SolidBrush(Color.White)) + g.FillEllipse(center, pole.X - core, pole.Y - core, core * 2, core * 2); + + // Orizzonte: una collina scura che ancora la scena alla terra. Sotto i venti pixel + // occuperebbe righe che servono alle scie, e sparisce. + if (!tiny) + { + using var horizon = new GraphicsPath(); + horizon.AddBezier( + new PointF(bounds.Left, s * 0.86f), + new PointF(s * 0.30f, s * 0.74f), + new PointF(s * 0.66f, s * 0.90f), + new PointF(bounds.Right, s * 0.79f)); + horizon.AddLine(bounds.Right, bounds.Bottom, bounds.Left, bounds.Bottom); + horizon.CloseFigure(); + + using var ground = new SolidBrush(Color.FromArgb(0xFF, 0x02, 0x03, 0x07)); + g.FillPath(ground, horizon); + } + + g.ResetClip(); + + // Bordo appena percettibile: stacca l'icona dagli sfondi chiari. + using (var path = RoundedRectangle(RectangleF.Inflate(bounds, -0.5f, -0.5f), s * 0.22f)) + using (var edge = new Pen(Color.FromArgb(60, 0x8A, 0xB4, 0xE8), Math.Max(1f, s * 0.012f))) + { + g.DrawPath(edge, path); + } + + return bitmap; + } + + private static GraphicsPath RoundedRectangle(RectangleF bounds, float radius) + { + var path = new GraphicsPath(); + float diameter = Math.Min(radius * 2, Math.Min(bounds.Width, bounds.Height)); + var arc = new RectangleF(bounds.X, bounds.Y, diameter, diameter); + + path.AddArc(arc, 180, 90); + arc.X = bounds.Right - diameter; + path.AddArc(arc, 270, 90); + arc.Y = bounds.Bottom - diameter; + path.AddArc(arc, 0, 90); + arc.X = bounds.X; + path.AddArc(arc, 90, 90); + path.CloseFigure(); + return path; + } + + // ------------------------------------------------------------------ codifica + + private static byte[] EncodePng(Bitmap bitmap) + { + using var stream = new MemoryStream(); + bitmap.Save(stream, ImageFormat.Png); + return stream.ToArray(); + } + + /// + /// DIB a 32 bit come lo vuole il formato ICO: intestazione con altezza doppia, pixel + /// dal basso verso l'alto e una maschera AND che resta comunque presente, anche se con + /// il canale alfa non serve più a nulla. + /// + private static byte[] EncodeDib(Bitmap bitmap) + { + int width = bitmap.Width; + int height = bitmap.Height; + int maskStride = (width + 31) / 32 * 4; + + var output = new byte[40 + width * height * 4 + maskStride * height]; + Span header = output.AsSpan(0, 40); + + BinaryPrimitives.WriteUInt32LittleEndian(header, 40); + BinaryPrimitives.WriteInt32LittleEndian(header[4..], width); + BinaryPrimitives.WriteInt32LittleEndian(header[8..], height * 2); + BinaryPrimitives.WriteUInt16LittleEndian(header[12..], 1); + BinaryPrimitives.WriteUInt16LittleEndian(header[14..], 32); + BinaryPrimitives.WriteUInt32LittleEndian(header[20..], (uint)(width * height * 4)); + + var locked = bitmap.LockBits(new Rectangle(0, 0, width, height), + ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); + try + { + for (int y = 0; y < height; y++) + { + IntPtr row = locked.Scan0 + y * locked.Stride; + int destination = 40 + (height - 1 - y) * width * 4; + System.Runtime.InteropServices.Marshal.Copy(row, output, destination, width * 4); + } + } + finally + { + bitmap.UnlockBits(locked); + } + + return output; + } +} diff --git a/Titano/Diagnostics/SelfTest.cs b/Titano/Diagnostics/SelfTest.cs index 0561577..c35e0d9 100644 --- a/Titano/Diagnostics/SelfTest.cs +++ b/Titano/Diagnostics/SelfTest.cs @@ -119,6 +119,25 @@ public static class SelfTest Add(checks, "Decodifica — luminanza invariante alla scala", worstScaleError < 0.12, $"scarto massimo {worstScaleError:0.000} EV fra piena risoluzione e versioni ridotte"); + // L'orientamento richiesto deve tradursi in pixel spostati davvero: si decodifica lo + // stesso file chiedendo lo specchiamento orizzontale e si confronta colonna per colonna. + using (var mirrored = ImageDecoder.Decode(paths[10], definition.Width, definition.Height, 2, pool)) + { + double worstMirrorError = 0; + for (int y = 4; y < definition.Height - 4; y += 7) + { + for (int x = 4; x < definition.Width - 4; x += 7) + { + int direct = frameA.Offset(x, y); + int flipped = mirrored.Offset(definition.Width - 1 - x, y); + worstMirrorError = Math.Max(worstMirrorError, + Math.Abs(frameA.Data[direct] - mirrored.Data[flipped])); + } + } + Add(checks, "Orientamento — lo specchiamento sposta i pixel", worstMirrorError < 1e-5, + $"scarto massimo {worstMirrorError:0.000000} fra fotogramma diretto e specchiato ribaltato"); + } + // ---------------------------------------------------------------- 5. optical flow var flowEngine = new OpticalFlowEngine(project.Flow); diff --git a/Titano/Diagnostics/SequenceDiagnostics.cs b/Titano/Diagnostics/SequenceDiagnostics.cs index 429da53..2aaf5bb 100644 --- a/Titano/Diagnostics/SequenceDiagnostics.cs +++ b/Titano/Diagnostics/SequenceDiagnostics.cs @@ -172,6 +172,7 @@ public static class SequenceDiagnostics var project = new Pipeline.TitanoProject(); project.Sequence = TimelapseSequence.Build(subset); project.Sequence.RecomputeTiming(); + project.DetectOrientation(); project.Export.OutputPath = outputPath; project.Export.FrameRate = 24; project.Export.BitrateMbps = 80; @@ -182,6 +183,8 @@ public static class SequenceDiagnostics var (requestedWidth, requestedHeight) = project.ResolveRequestedSize(); long perFrame = (long)width * height * 3 * sizeof(float); output.WriteLine($" fotogrammi {subset.Count}"); + output.WriteLine($" orientamento {project.Orientation.Description} ({project.Orientation.Reason})"); + output.WriteLine($" profilo di qualità {project.General.Quality}"); output.WriteLine($" risoluzione {width}×{height}" + (width != requestedWidth || height != requestedHeight ? $" (ridotta da {requestedWidth}×{requestedHeight} per il limite del codec)" diff --git a/Titano/Imaging/ImageDecoder.cs b/Titano/Imaging/ImageDecoder.cs index 69f7f5d..b759670 100644 --- a/Titano/Imaging/ImageDecoder.cs +++ b/Titano/Imaging/ImageDecoder.cs @@ -165,6 +165,20 @@ public static class ImageDecoder public static bool SwapsAxes(int orientation) => orientation is 5 or 6 or 7 or 8; + /// Descrizione leggibile di una trasformazione Exif. + public static string DescribeOrientation(int orientation) => orientation switch + { + 1 => "nessuna trasformazione", + 2 => "specchiata orizzontalmente", + 3 => "ruotata di 180°", + 4 => "specchiata verticalmente", + 5 => "trasposta", + 6 => "ruotata di 90° in senso orario", + 7 => "trasversa", + 8 => "ruotata di 90° in senso antiorario", + _ => "sconosciuta", + }; + // ------------------------------------------------------------------ conversione + orientamento private static unsafe void Convert32(byte[] scratch, int stride, int width, int height, diff --git a/Titano/Imaging/OrientationDetector.cs b/Titano/Imaging/OrientationDetector.cs new file mode 100644 index 0000000..27a5641 --- /dev/null +++ b/Titano/Imaging/OrientationDetector.cs @@ -0,0 +1,62 @@ +using Titano.Metadata; + +namespace Titano.Imaging; + +/// Esito del rilevamento automatico dell'orientamento. +public sealed record OrientationDetection(int Orientation, string Reason) +{ + public string Description => ImageDecoder.DescribeOrientation(Orientation); +} + +/// +/// Determina quale trasformazione applicare ai pixel dopo la decodifica. +/// +/// Il tag Exif dice come va raddrizzata l'immagine, ma non basta leggerlo: alcuni +/// decodificatori di sistema lo applicano già per conto proprio e altri no, e la stessa +/// installazione di Windows si comporta diversamente a seconda del formato. Applicare la +/// trasformazione una seconda volta produce fotogrammi ribaltati; non applicarla mai +/// produce sequenze coricate. Qui si confronta ciò che il decodificatore restituisce con +/// ciò che il file dichiara di contenere, e si decide di conseguenza. +/// +public static class OrientationDetector +{ + public static OrientationDetection Detect(FrameMetadata metadata) + { + int declared = metadata.Orientation; + if (declared is < 1 or > 8) + return new OrientationDetection(1, "il file non dichiara un orientamento valido"); + + if (declared == 1) + return new OrientationDetection(1, "il file non richiede trasformazioni"); + + // Le dimensioni memorizzate provengono dalla directory dell'immagine principale, + // quindi descrivono i pixel così come stanno nel file, prima di ogni rotazione. + int storedWidth = metadata.PixelWidth; + int storedHeight = metadata.PixelHeight; + if (storedWidth <= 0 || storedHeight <= 0) + return new OrientationDetection(declared, "dimensioni dichiarate assenti, si applica il tag Exif"); + + int decodedWidth, decodedHeight; + try + { + (decodedWidth, decodedHeight) = ImageDecoder.ProbeDisplaySize(metadata.FilePath, 1); + } + catch (Exception) + { + return new OrientationDetection(declared, "sonda non riuscita, si applica il tag Exif"); + } + + if (decodedWidth <= 0 || decodedHeight <= 0) + return new OrientationDetection(declared, "sonda senza esito, si applica il tag Exif"); + + // Se la trasformazione scambia gli assi e il decodificatore restituisce già le + // dimensioni scambiate, l'ha applicata lui: riapplicarla ribalterebbe il fotogramma. + if (ImageDecoder.SwapsAxes(declared) && + decodedWidth == storedHeight && decodedHeight == storedWidth) + { + return new OrientationDetection(1, "già raddrizzata dal decodificatore di sistema"); + } + + return new OrientationDetection(declared, "dal tag Exif del file"); + } +} diff --git a/Titano/Pipeline/RenderPipeline.cs b/Titano/Pipeline/RenderPipeline.cs index f989f1d..b1ce3c6 100644 --- a/Titano/Pipeline/RenderPipeline.cs +++ b/Titano/Pipeline/RenderPipeline.cs @@ -78,6 +78,7 @@ public sealed class RenderPipeline(TitanoProject project) int analysisHeight = Math.Max(2, (int)Math.Round(analysisWidth * workingHeight / (double)workingWidth)); int parallelism = Math.Clamp(_project.General.DecodeParallelism, 1, 16); + int orientation = _project.EffectiveOrientation; var pool = new FrameBufferPool(parallelism + 2); var stats = new LuminanceStats[count]; var failures = new bool[count]; @@ -97,7 +98,7 @@ public sealed class RenderPipeline(TitanoProject project) try { using var buffer = ImageDecoder.Decode(record.FilePath, analysisWidth, analysisHeight, - record.Metadata.Orientation, pool); + orientation, pool); stats[i] = LuminanceAnalyzer.Analyze(buffer); } catch (Exception ex) when (ex is not OperationCanceledException) @@ -184,6 +185,7 @@ public sealed class RenderPipeline(TitanoProject project) var curve = _project.Curve; int parallelism = Math.Clamp(_project.General.DecodeParallelism, 1, 16); + int orientation = _project.EffectiveOrientation; // Buffer contemporaneamente vivi: i cinque fissi del ciclo (corrente, successivo, // sfocatura, interpolato, interpolato sfocato), le decodifiche in volo e la copia @@ -216,8 +218,8 @@ public sealed class RenderPipeline(TitanoProject project) { cancellation.ThrowIfCancellationRequested(); int index = i; - var task = Task.Run(() => DecodeAndCorrect(sequence, index, width, height, pool, - curve, deflickerSettings), cancellation); + var task = Task.Run(() => DecodeAndCorrect(sequence, index, width, height, orientation, + pool, curve, deflickerSettings), cancellation); await channel.Writer.WriteAsync(task, cancellation).ConfigureAwait(false); } channel.Writer.Complete(); @@ -342,14 +344,14 @@ public sealed class RenderPipeline(TitanoProject project) /// Decodifica un fotogramma e vi applica il guadagno di esposizione calcolato. private static ImageBuffer? DecodeAndCorrect(TimelapseSequence sequence, int index, int width, int height, - FrameBufferPool pool, DeflickerCurve? curve, + int orientation, FrameBufferPool pool, DeflickerCurve? curve, DeflickerSettings settings) { var record = sequence.Frames[index]; ImageBuffer buffer; try { - buffer = ImageDecoder.Decode(record.FilePath, width, height, record.Metadata.Orientation, pool); + buffer = ImageDecoder.Decode(record.FilePath, width, height, orientation, pool); } catch (Exception) { diff --git a/Titano/Pipeline/TitanoProject.cs b/Titano/Pipeline/TitanoProject.cs index bcc3f64..d68cbf4 100644 --- a/Titano/Pipeline/TitanoProject.cs +++ b/Titano/Pipeline/TitanoProject.cs @@ -5,6 +5,18 @@ using Titano.Video; namespace Titano.Pipeline; +/// +/// Compromesso fra qualità del risultato e tempo di elaborazione. Agisce sulla finezza +/// del campo di movimento, sui campioni della sfocatura e sulla risoluzione della passata +/// fotometrica: tutte grandezze che migliorano il risultato e costano tempo. +/// +public enum QualityProfile +{ + Bozza, + Standard, + Massima, +} + /// Impostazioni della sezione "Generale". public sealed class GeneralSettings { @@ -12,7 +24,15 @@ public sealed class GeneralSettings public int WorkingWidth { get; set; } /// Larghezza usata nella passata di analisi fotometrica: incide solo sulla velocità. - public int AnalysisWidth { get; set; } = 1024; + public int AnalysisWidth { get; set; } = 2048; + + /// + /// Trasformazione da applicare ai pixel decodificati; null lascia decidere al rilevamento + /// automatico. Serve quando i metadati del file sono sbagliati e l'occhio dice altro. + /// + public int? OrientationOverride { get; set; } + + public QualityProfile Quality { get; set; } = QualityProfile.Massima; /// Decodifiche simultanee. Limita anche i buffer in volo, quindi la memoria occupata. public int DecodeParallelism { get; set; } = Math.Clamp(Environment.ProcessorCount / 2, 2, 8); @@ -35,6 +55,14 @@ public sealed class TitanoProject public OpticalFlowSettings Flow { get; set; } = new(); public ExportSettings Export { get; set; } = new(); + public TitanoProject() + { + // I valori predefiniti delle singole sezioni devono corrispondere al profilo + // dichiarato, altrimenti l'interfaccia mostrerebbe "Massima" con i parametri di + // "Standard" sotto i cursori. + ApplyQualityProfile(General.Quality); + } + public TimelapseSequence? Sequence { get; set; } /// Curva di deflicker dell'ultima analisi, usata dal grafico e dall'esportazione. @@ -46,6 +74,67 @@ public sealed class TitanoProject public bool HasSequence => Sequence is { Count: > 0 }; public bool IsAnalyzed => Curve is not null && Stats is not null; + /// Orientamento dedotto dal primo fotogramma della sequenza. + public Imaging.OrientationDetection Orientation { get; private set; } = new(1, "nessuna sequenza caricata"); + + /// Trasformazione effettivamente applicata: la scelta dell'utente ha la precedenza. + public int EffectiveOrientation => General.OrientationOverride ?? Orientation.Orientation; + + /// + /// Esamina il primo fotogramma per stabilire come vanno raddrizzati i pixel. Va invocato + /// dopo l'ingestion: la sequenza è omogenea, quindi un solo campione basta per tutti. + /// + public void DetectOrientation() + { + Orientation = Sequence is { Count: > 0 } + ? Imaging.OrientationDetector.Detect(Sequence.Frames[0].Metadata) + : new Imaging.OrientationDetection(1, "nessuna sequenza caricata"); + } + + /// + /// Allinea i parametri di elaborazione al profilo di qualità scelto. Sovrascrive i + /// cursori delle sezioni avanzate: è il senso stesso di un profilo. + /// + public void ApplyQualityProfile(QualityProfile profile) + { + General.Quality = profile; + + switch (profile) + { + case QualityProfile.Bozza: + General.AnalysisWidth = 768; + Flow.AnalysisWidth = 640; + Flow.CellSize = 12; + Flow.PyramidLevels = 3; + Flow.WindowRadius = 5; + Flow.Iterations = 3; + MotionBlur.MaxSamples = 13; + break; + + case QualityProfile.Standard: + General.AnalysisWidth = 1024; + Flow.AnalysisWidth = 960; + Flow.CellSize = 8; + Flow.PyramidLevels = 4; + Flow.WindowRadius = 6; + Flow.Iterations = 5; + MotionBlur.MaxSamples = 25; + break; + + default: + // Il campo di movimento si infittisce e la scia guadagna campioni: sono le + // due voci che si vedono davvero nel fotogramma finale. + General.AnalysisWidth = 2048; + Flow.AnalysisWidth = 1440; + Flow.CellSize = 6; + Flow.PyramidLevels = 5; + Flow.WindowRadius = 7; + Flow.Iterations = 8; + MotionBlur.MaxSamples = 49; + break; + } + } + /// /// Risoluzione di lavoro effettiva: parte dal primo fotogramma, applica l'eventuale limite /// dell'utente e arrotonda a valori pari, richiesti dal sottocampionamento cromatico 4:2:0. @@ -55,14 +144,15 @@ public sealed class TitanoProject if (Sequence is not { Count: > 0 }) return (0, 0); var first = Sequence.Frames[0].Metadata; + int orientation = EffectiveOrientation; int sourceWidth = first.PixelWidth; int sourceHeight = first.PixelHeight; if (sourceWidth <= 0 || sourceHeight <= 0) { - (sourceWidth, sourceHeight) = Imaging.ImageDecoder.ProbeDisplaySize(first.FilePath, first.Orientation); + (sourceWidth, sourceHeight) = Imaging.ImageDecoder.ProbeDisplaySize(first.FilePath, orientation); } - else if (Imaging.ImageDecoder.SwapsAxes(first.Orientation)) + else if (Imaging.ImageDecoder.SwapsAxes(orientation)) { (sourceWidth, sourceHeight) = (sourceHeight, sourceWidth); } @@ -98,7 +188,7 @@ public sealed class TitanoProject int sourceWidth = first.PixelWidth; int sourceHeight = first.PixelHeight; if (sourceWidth <= 0 || sourceHeight <= 0) return (width, height); - if (Imaging.ImageDecoder.SwapsAxes(first.Orientation)) (sourceWidth, sourceHeight) = (sourceHeight, sourceWidth); + if (Imaging.ImageDecoder.SwapsAxes(EffectiveOrientation)) (sourceWidth, sourceHeight) = (sourceHeight, sourceWidth); int requestedWidth = Export.Width > 0 ? Export.Width : General.WorkingWidth > 0 ? Math.Min(General.WorkingWidth, sourceWidth) diff --git a/Titano/Program.cs b/Titano/Program.cs index 2774098..143ef6c 100644 --- a/Titano/Program.cs +++ b/Titano/Program.cs @@ -26,6 +26,14 @@ internal static class Program return SelfTest.Run(directory, Console.Out); } + if (args.Length > 1 && args[0] == "--make-icon") + { + EnsureConsole(); + IconGenerator.Write(args[1]); + Console.WriteLine($"Icona generata in {args[1]}."); + return 0; + } + if (args.Length > 1 && args[0] == "--diagnose") { EnsureConsole(); diff --git a/Titano/README.md b/Titano/README.md index a952f32..312a19f 100644 --- a/Titano/README.md +++ b/Titano/README.md @@ -84,6 +84,18 @@ miniatura, e le dimensioni vere vivono in una SubIFD marcata come non ridotta. T la directory dell'immagine principale invece di fidarsi della prima, e legge i metadati con una finestra che si estende su richiesta: di un DNG da 15 MiB ne tocca 128 KiB. +**Orientamento.** Il tag Exif dice come raddrizzare l'immagine, ma non basta leggerlo: +alcuni decodificatori di sistema lo applicano già per conto proprio e altri no. Applicarlo +una seconda volta ribalta il fotogramma, non applicarlo mai lo lascia coricato. In modalità +automatica Titano confronta le dimensioni che il decodificatore restituisce con quelle che il +file dichiara di contenere e decide di conseguenza; la sezione Generale mostra cosa ha dedotto +e permette di imporre una trasformazione diversa, con riscontro immediato nell'anteprima. + +**Qualità.** Il profilo di elaborazione governa finezza della griglia del campo vettoriale, +livelli della piramide, iterazioni per livello, campioni per pixel della scia e risoluzione +della passata fotometrica. Il valore predefinito è "Massima": costa circa tre volte il tempo +di "Standard" e si vede nei bordi dei soggetti in movimento. + La risoluzione di lavoro viene infine ricondotta ai limiti del codec. Un encoder hardware accetta volentieri un fotogramma 4000×3000 e dichiara un livello alto: il flusso è formalmente valido ma supera il Livello 5.2 di H.264, e i decodificatori comuni si rifiutano di aprirlo. Il @@ -108,7 +120,7 @@ Titano.exe --selftest [cartella] Genera una sequenza sintetica dalle proprietà note — traslazione, sfarfallio e rampa di luce imposti, pausa dell'intervallometro inclusa — e la fa attraversare l'intera pipeline, -confrontando 24 grandezze misurate con i valori attesi: campi Exif, cadenza, shutter angle, +confrontando 25 grandezze misurate con i valori attesi: campi Exif, cadenza, shutter angle, riduzione dello sfarfallio, conservazione della rampa, invarianza della luminanza alla scala di decodifica, modulo e direzione del campo vettoriale, attenuazione del dettaglio dovuta alla sfocatura, allineamento delle NAL dentro ogni campione del contenitore e — prova conclusiva — @@ -129,3 +141,12 @@ Titano.exe --capture [cartella-sequenza] [scheda] ``` Cattura l'interfaccia in un'immagine, per verificarne la resa in modo riproducibile. + +``` +Titano.exe --make-icon +``` + +Ridisegna l'icona dell'applicazione — scie stellari attorno al polo celeste, l'immagine in cui +il tempo diventa visibile — e ne scrive il contenitore ICO a mano. I livelli sotto i venti +pixel ricevono meno archi e più spessi: alla dimensione della barra delle applicazioni sette +scie sottili diventerebbero una macchia. diff --git a/Titano/Titano.csproj b/Titano/Titano.csproj index ac297ec..806def8 100644 --- a/Titano/Titano.csproj +++ b/Titano/Titano.csproj @@ -20,6 +20,7 @@ x64 x64 Titano + Titano.ico Titano PerMonitorV2 Segoe UI, 9pt diff --git a/Titano/Titano.ico b/Titano/Titano.ico new file mode 100644 index 0000000000000000000000000000000000000000..6c7366c3effa90b46d8bfdd8eabcd6920175848c GIT binary patch literal 142682 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 &u|v7FORol=+4oorDhCKJ>C@YHJd)~ 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%0DA%+$ZuK-&RH?BLh(OR~dc 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 + /// Riprende l'icona dalle risorse dell'eseguibile: è la stessa che Esplora risorse + /// mostra sul file, quindi finestra, barra delle applicazioni e cartella restano coerenti. + /// + private void LoadApplicationIcon() + { + try + { + if (Environment.ProcessPath is { } executable) Icon = Icon.ExtractAssociatedIcon(executable); + } + catch (Exception) + { + // Senza icona la finestra usa quella predefinita: nulla di grave. + } + } + protected override void OnHandleCreated(EventArgs e) { base.OnHandleCreated(e); @@ -337,6 +354,8 @@ internal sealed class MainForm : Form _project.Sequence = sequence; _project.Curve = null; _project.Stats = null; + _project.DetectOrientation(); + _settings.ShowDetectedOrientation(_project.Orientation); _table.SetSequence(sequence); _chart.SetData(sequence, null); @@ -345,7 +364,9 @@ internal sealed class MainForm : Form ShowPreview(0); SetStatus($"{sequence.Count} fotogrammi caricati. Cadenza nominale {sequence.NominalInterval:0.###} s, " + - $"{sequence.CadenceAnomalies} intervalli anomali." + DescribeResolutionClamp()); + $"{sequence.CadenceAnomalies} intervalli anomali. " + + $"Orientamento {_project.Orientation.Description} ({_project.Orientation.Reason})." + + DescribeResolutionClamp()); } catch (OperationCanceledException) { diff --git a/Titano/UI/PreviewPanel.cs b/Titano/UI/PreviewPanel.cs index b4de450..5967660 100644 --- a/Titano/UI/PreviewPanel.cs +++ b/Titano/UI/PreviewPanel.cs @@ -116,12 +116,13 @@ internal sealed class PreviewPanel : Control { var record = sequence.Frames[index]; var metadata = record.Metadata; + int orientation = project.EffectiveOrientation; int sourceWidth = metadata.PixelWidth; int sourceHeight = metadata.PixelHeight; if (sourceWidth <= 0 || sourceHeight <= 0) - (sourceWidth, sourceHeight) = ImageDecoder.ProbeDisplaySize(metadata.FilePath, metadata.Orientation); - else if (ImageDecoder.SwapsAxes(metadata.Orientation)) + (sourceWidth, sourceHeight) = ImageDecoder.ProbeDisplaySize(metadata.FilePath, orientation); + else if (ImageDecoder.SwapsAxes(orientation)) (sourceWidth, sourceHeight) = (sourceHeight, sourceWidth); if (sourceWidth <= 0 || sourceHeight <= 0) return (null, "Immagine non leggibile"); @@ -132,7 +133,7 @@ internal sealed class PreviewPanel : Control int height = Math.Max(16, (int)(sourceHeight * scale) & ~1); var pool = new FrameBufferPool(4); - using var frame = ImageDecoder.Decode(metadata.FilePath, width, height, metadata.Orientation, pool); + using var frame = ImageDecoder.Decode(metadata.FilePath, width, height, orientation, pool); token.ThrowIfCancellationRequested(); var status = new System.Text.StringBuilder(); @@ -152,7 +153,7 @@ internal sealed class PreviewPanel : Control if (project.MotionBlur.Enabled && index + 1 < sequence.Count) { var nextMetadata = sequence.Frames[index + 1].Metadata; - using var next = ImageDecoder.Decode(nextMetadata.FilePath, width, height, nextMetadata.Orientation, pool); + using var next = ImageDecoder.Decode(nextMetadata.FilePath, width, height, orientation, pool); token.ThrowIfCancellationRequested(); if (project.Deflicker.Enabled && curve is not null && index + 1 < curve.Count) diff --git a/Titano/UI/SettingsPanel.cs b/Titano/UI/SettingsPanel.cs index a3c3177..290b855 100644 --- a/Titano/UI/SettingsPanel.cs +++ b/Titano/UI/SettingsPanel.cs @@ -11,9 +11,13 @@ namespace Titano.UI; /// internal sealed class SettingsPanel : Panel { + /// Valori Exif corrispondenti alle voci del menu dell'orientamento; null = automatico. + private static readonly int?[] OrientationValues = [null, 1, 2, 4, 6, 3, 8]; + private readonly TitanoProject _project; private readonly TabStrip _tabs; private readonly Panel[] _pages; + private LabeledCombo? _orientationCombo; /// Un parametro del deflicker è cambiato: basta ricalcolare la curva. public event EventHandler? DeflickerChanged; @@ -66,6 +70,18 @@ internal sealed class SettingsPanel : Panel ShowPage(0); } + /// + /// Riporta nel menu l'esito del rilevamento, così la voce "Automatico" dice cosa ha deciso. + /// + public void ShowDetectedOrientation(Imaging.OrientationDetection detection) + { + if (_orientationCombo is null || _orientationCombo.Combo.Items.Count == 0) return; + + int selected = _orientationCombo.Combo.SelectedIndex; + _orientationCombo.Combo.Items[0] = $"Automatico — {detection.Description}"; + _orientationCombo.Combo.SelectedIndex = selected; + } + /// Seleziona una delle tre sezioni; usata anche dalla modalità di cattura. internal void SelectPage(int index) => _tabs.SelectedIndex = index; @@ -97,6 +113,37 @@ internal sealed class SettingsPanel : Panel AnalysisInvalidated?.Invoke(this, EventArgs.Empty); })); + _orientationCombo = Combo("Orientamento dei fotogrammi", + ["Automatico", "Nessuna rotazione", "Specchia orizzontalmente", "Specchia verticalmente", + "Ruota 90° in senso orario", "Ruota 180°", "Ruota 90° in senso antiorario"], + Array.IndexOf(OrientationValues, _project.General.OrientationOverride), + index => + { + _project.General.OrientationOverride = OrientationValues[Math.Clamp(index, 0, OrientationValues.Length - 1)]; + AnalysisInvalidated?.Invoke(this, EventArgs.Empty); + }); + stack.Add(_orientationCombo); + + stack.Add(Note("In automatico l'orientamento viene dedotto dai metadati del primo scatto e " + + "confrontato con ciò che il decodificatore di sistema restituisce, così la " + + "trasformazione non viene applicata due volte. Se il risultato non convince, " + + "l'anteprima mostra subito l'effetto di una scelta manuale.")); + + stack.Add(new SectionHeader("Qualità di elaborazione")); + + stack.Add(Combo("Profilo", + ["Bozza — veloce", "Standard — equilibrio", "Massima — resa migliore"], + (int)_project.General.Quality, + index => + { + _project.ApplyQualityProfile((QualityProfile)Math.Clamp(index, 0, 2)); + AnalysisInvalidated?.Invoke(this, EventArgs.Empty); + })); + + stack.Add(Note("Il profilo governa finezza del campo vettoriale, campioni della sfocatura e " + + "risoluzione della passata fotometrica, e sovrascrive i cursori delle sezioni " + + "avanzate. Più qualità significa più tempo, non un file più grande.")); + stack.Add(new SectionHeader("Prestazioni")); stack.Add(Slider("Larghezza della passata di analisi", 256, 2048, _project.General.AnalysisWidth, 64, "0", "px", value => @@ -282,6 +329,16 @@ internal sealed class SettingsPanel : Panel control.Top = _y; control.Width = Panel.ClientSize.Width - 34; control.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right; + + // Le note esplicative variano in lunghezza: si misurano sulla larghezza reale + // della colonna, altrimenti le più lunghe finirebbero tagliate a metà frase. + if (control is Label note && !note.AutoSize) + { + var measured = TextRenderer.MeasureText(note.Text, note.Font, + new Size(control.Width, 0), TextFormatFlags.WordBreak); + control.Height = measured.Height + 8; + } + Panel.Controls.Add(control); _y += control.Height + 6; }