private static bool TryGetTimestamps(string path, out DateTime fsCreated, out DateTime fsModified, out DateTime exifCreated) { exifCreated = default(DateTime); fsCreated = File.GetCreationTime(path); fsModified = File.GetLastWriteTime(path); return(ImageClient.QueryTimestampProperty(new Profile(), path, out exifCreated)); }
// use highest quality transforms and no cached bitmaps public static void FinalOutputOne(Item item, Stats stats, Dictionary <string, bool> createdFiles, List <string> messages, CancellationTokenSource cancel) { Profile profile = new Profile("FinalOutput {0}", item.RenamedFileName); profile.Push("Item.WaitInit"); item.WaitInit(); profile.Pop(); DateTime originalCreationTime = File.GetCreationTime(item.SourcePath); DateTime originalLastWriteTime = File.GetLastWriteTime(item.SourcePath); string renamedTargetPath = Path.Combine(Path.GetDirectoryName(item.TargetPath), item.RenamedFileName); if (item.Delete) { // actual deletion occurs after end of run, by file being omitted from createdFiles Interlocked.Increment(ref stats.deleted); } else if (item.Valid) { // load source string tempFile = Path.GetTempFileName(); File.Copy(item.SourcePath, tempFile, true /*overwrite*/); SmartBitmap bitmap = null; bool jpegTranRotationRequired = true; // initial lossy transforms if (item.NormalizeGeometry || (item.FineRotateDegrees != 0)) { if (bitmap == null) { bitmap = new SmartBitmap(Transforms.LoadAndOrientGDI(tempFile, profile)); } jpegTranRotationRequired = false; if (item.RightRotations != 0) { ManagedBitmap bitmap2 = bitmap.AsManaged().RotateFlip(Transforms.RotateFlipFromRightRotations(item.RightRotations)); bitmap.Dispose(); bitmap = new SmartBitmap(bitmap2); } Transforms.ApplyNormalizeGeometry( profile, item.SourceFileName, bitmap.AsManaged(), 1, new Transforms.NormalizeGeometryParameters( item.CornerTL, item.CornerTR, item.CornerBL, item.CornerBR, item.NormalizeGeometryForcedAspectRatio, item.FineRotateDegrees, item.NormalizeGeometryFinalInterp), delegate(string text) { }, cancel); } // lossless transform phase if (bitmap == null) { List <string> jpegtranMessages = new List <string>(); string error; bool rotateOK = true; if (jpegTranRotationRequired && ((item.RightRotations != 0) || (item.OriginalExifOrientation != RotateFlipType.RotateNoneFlipNone))) { rotateOK = false; int combinedRotations = (item.RightRotations + Transforms.RightRotationsFromRotateFlipType(item.OriginalExifOrientation)) % 4; // TODO: strip only Exif orientation after doing this - how? (currently strips all) if (Transforms.LosslessRotateRightFinal( tempFile, tempFile, combinedRotations, out error)) { Interlocked.Increment(ref stats.rotated); rotateOK = true; } else { jpegtranMessages.Add(String.Format("Lossless rotate failed for \"{0}\" ({1}).", item.RenamedFileName, error)); } } bool cropOK = true; if (!item.CropRect.IsEmpty) { cropOK = false; if (Transforms.LosslessCropFinal(tempFile, tempFile, item.CropRect, out error)) { Interlocked.Increment(ref stats.cropped); cropOK = true; } else { jpegtranMessages.Add(String.Format("Lossless crop failed for \"{0}\" ({1}).", item.RenamedFileName, error)); } } if (!rotateOK || !cropOK) { // If jpegtran is unable to handle image (e.g. dimensions not integral multiples - as generated by // some cameras), fall back to lossy rotation and log a message. bitmap = new SmartBitmap(Transforms.LoadAndOrientGDI(tempFile, profile)); if (!rotateOK) { ManagedBitmap bitmap2 = bitmap.AsManaged().RotateFlip(Transforms.RotateFlipFromRightRotations(item.RightRotations)); bitmap.Dispose(); bitmap = new SmartBitmap(bitmap2); } if (!cropOK) { ManagedBitmap bitmap2 = bitmap.AsManaged().Crop(item.CropRect); bitmap.Dispose(); bitmap = new SmartBitmap(bitmap2); } jpegtranMessages.Add("Using lossy rotation/crop instead."); lock (messages) { messages.Add(String.Join(" ", jpegtranMessages.ToArray())); } } } else { // whoops- preceding transform prevents jpegtran from being used (recreating jpeg would be lossy) if (!item.CropRect.IsEmpty) { ManagedBitmap bitmap2 = bitmap.AsManaged().Crop(item.CropRect); bitmap.Dispose(); bitmap = new SmartBitmap(bitmap2); } } // following lossy transforms phase if (item.Unbias) { if (bitmap == null) { bitmap = new SmartBitmap(Transforms.LoadAndOrientGDI(tempFile, profile)); } Transforms.PolyUnbiasDiagnostics unused; Transforms.ApplyPolyUnbias( profile, item.RenamedFileName, bitmap.AsManaged(profile), Rectangle.Empty /*already cropped*/, new Transforms.PolyUnbiasParameters( item.UnbiasMaxDegree, item.UnbiasMaxChisq, item.UnbiasMaxS, item.UnbiasMinV), null, out unused, cancel); Interlocked.Increment(ref stats.polyUnbias); } if (item.BrightAdjust) { if (bitmap == null) { bitmap = new SmartBitmap(Transforms.LoadAndOrientGDI(tempFile, profile)); } Transforms.ApplyBrightAdjust( profile, item.RenamedFileName, bitmap.AsManaged(profile), Rectangle.Empty /*already cropped*/, new Transforms.BrightAdjustParameters(item.BrightAdjustMinClusterFrac, item.BrightAdjustWhiteCorrect), null, cancel); Interlocked.Increment(ref stats.brightAdjust); } if (item.StaticSaturate) { if (bitmap == null) { bitmap = new SmartBitmap(Transforms.LoadAndOrientGDI(tempFile, profile)); } Transforms.ApplyStaticSaturation( profile, item.RenamedFileName, bitmap.AsManaged(profile), Rectangle.Empty /*already cropped*/, new Transforms.StaticSaturateParameters(item.StaticSaturateWhiteThreshhold, item.StaticSaturateBlackThreshhold, item.StaticSaturateExponent), cancel); } if (item.Shrink) { if (bitmap == null) { bitmap = new SmartBitmap(Transforms.LoadAndOrientGDI(tempFile, profile)); } //Bitmap shrunk = Transforms.Shrink(profile, bitmap.AsGDI(profile), item.ShrinkFactor); int newWidth = (int)Math.Floor(bitmap.Width / item.ShrinkFactor); int newHeight = (int)Math.Floor(bitmap.Height / item.ShrinkFactor); ManagedBitmap bitmap2 = ImageClient.ResizeGDI(profile, bitmap.AsManaged(profile), newWidth, newHeight); bitmap.Dispose(); bitmap = new SmartBitmap(bitmap2); Interlocked.Increment(ref stats.shrunk); } // TODO: eliminate shrink and OneBit's expand if both are configured if (item.OneBit) { if (bitmap == null) { bitmap = new SmartBitmap(Transforms.LoadAndOrientGDI(tempFile, profile)); } SmartBitmap bitmap2 = new SmartBitmap( Transforms.ApplyOneBit( profile, item.RenamedFileName, bitmap, new Transforms.OneBitParameters(item.OneBitChannel, item.OneBitThreshhold, item.OneBitScaleUp), cancel)); bitmap.Dispose(); bitmap = bitmap2; Interlocked.Increment(ref stats.oneBit); } if (bitmap != null) { Transforms.SaveImage(profile, bitmap, tempFile, item.JpegQuality, item.JpegUseGdi, item.OutputFormat); bitmap.Dispose(); } // write target string targetPath; bool extUpper = String.Equals(Path.GetExtension(renamedTargetPath), Path.GetExtension(renamedTargetPath).ToUpper()); switch (item.OutputFormat) { default: Debug.Assert(false); throw new ArgumentException(); case OutputFormat.Jpeg: targetPath = Path.ChangeExtension(renamedTargetPath, extUpper ? ".JPG" : ".jpg"); break; case OutputFormat.Bmp: targetPath = Path.ChangeExtension(renamedTargetPath, extUpper ? ".BMP" : ".bmp"); break; case OutputFormat.Png: targetPath = Path.ChangeExtension(renamedTargetPath, extUpper ? ".PNG" : ".png"); break; } for (int i = 0; i <= RetryCount; i++) { try { File.Copy(tempFile, targetPath, true /*overwrite*/); File.SetCreationTime(targetPath, originalCreationTime); File.SetLastWriteTime(targetPath, DateTime.Now); } catch (IOException) when(i < RetryCount) { // HACK: If folder is open, Explorer may have file locked to refresh thumbnail Thread.Sleep(SleepRetry); } } lock (createdFiles) { createdFiles.Add(Path.GetFileName(targetPath).ToLowerInvariant(), false); } File.Delete(tempFile); } else { lock (createdFiles) { createdFiles.Add(Path.GetFileName(renamedTargetPath).ToLowerInvariant(), false); } if (!String.Equals(item.SourcePath, renamedTargetPath, StringComparison.OrdinalIgnoreCase)) { File.Copy(item.SourcePath, renamedTargetPath, true /*overwrite*/); } } profile.End(); Program.Log(LogCat.Perf, profile.Report()); }
public static void Main(string[] args) { try { if ((args.Length >= 1) && String.Equals(args[0], "-waitdebugger")) { while (!Debugger.IsAttached) { Thread.Sleep(250); } ShiftArgs(ref args, 1); } if ((args.Length == 2) && String.Equals(args[0], "-server")) { Environment.ExitCode = ImageServer.RunServer(args[1]); return; } Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); bool showLogOnStart = false; while (args.Length >= 1) { switch (args[0]) { default: goto Break; case "-profile": ProfileMode = true; ShiftArgs(ref args, 1); break; case "-showlog": showLogOnStart = true; ShiftArgs(ref args, 1); break; case "-swap": EnableSwap = true; ShiftArgs(ref args, 1); break; case "-noswap": EnableSwap = false; ShiftArgs(ref args, 1); break; } } Break: if (args.Length == 0) { // HACK: based on what WinMerge does - usability is suspect but still much better than FolderBrowserDialog. // See: http://www.codeproject.com/Articles/44914/Select-file-or-folder-from-the-same-dialog using (OpenFileDialog dialog = new OpenFileDialog()) { dialog.ValidateNames = false; dialog.CheckFileExists = false; dialog.CheckPathExists = false; dialog.Title = "Select a Folder"; dialog.FileName = "Select Folder."; if (dialog.ShowDialog() != DialogResult.OK) { return; } args = new string[] { Path.GetDirectoryName(dialog.FileName) }; } } if (args.Length != 1) { MessageBox.Show("Program must take one argument which is the path to a directory containing images to adjust."); return; } Window window; ImageCache cache = new ImageCache(); string directory = Path.GetFullPath(args[0]); string scanDirectory = GetScanDirectoryFromTargetDirectory(directory); if (!Directory.Exists(directory)) { MessageBox.Show(String.Format("Specified directory does not exist: \"{0}\"", directory)); return; } XmlDocument settings = new XmlDocument(); { string settingsPath = Path.Combine(scanDirectory, SettingsFile); if (File.Exists(settingsPath)) { settings.Load(settingsPath); } } GlobalOptions options = new GlobalOptions(settings.CreateNavigator().SelectSingleNode("/*/options")); { using (GlobalOptionsDialog dialog = new GlobalOptionsDialog(options, directory)) { Application.Run(dialog); if (dialog.DialogResult != DialogResult.OK) { return; } } } if (showLogOnStart) { logWindow = new LoggingWindow(); logWindow.Show(); } // scan items List <Item> items = new List <Item>(); foreach (string filePath in Directory.GetFiles(scanDirectory)) { string fileName = Path.GetFileName(filePath); if (String.Equals(fileName, SettingsFile, StringComparison.OrdinalIgnoreCase)) { continue; } bool extUpper = String.Equals(Path.GetExtension(fileName), Path.GetExtension(fileName).ToUpper()); //fileName = Path.ChangeExtension(fileName, extUpper ? ".JPG" : ".jpg"); Item item = new Item(Path.Combine(directory, fileName), options, cache); items.Add(item); XPathNavigator itemNav = settings.CreateNavigator().SelectSingleNode(String.Format("/*/items/item[file=\"{0}\"]", fileName)); if (itemNav != null) { item.ReadXml(itemNav); } } window = new Window(directory, items, cache, options); window.Show(); window.LastAnalysisTask = BatchAnalyzerQueue.BeginAnalyzeBatch(items); Application.Run(window); LoggingWindow logw = logWindow; if (logw != null) { logw.Close(); } window.LastAnalysisTask.Wait(); // allow any unfinished actions to cancel and dispose state cache.Dispose(); SerializationManager.Manager.Dispose(); ImageClient.Close(); } catch (Exception exception) { Debugger.Log(0, null, exception.ToString()); MessageBox.Show(exception.ToString()); } }
public static void Main(string[] args) { try { if ((args.Length >= 1) && String.Equals(args[0], "-waitdebugger")) { while (!Debugger.IsAttached) { Thread.Sleep(250); } ShiftArgs(ref args, 1); } if ((args.Length == 2) && String.Equals(args[0], "-server")) { Environment.ExitCode = ImageServer.RunServer(args[1]); return; } Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); bool showLogOnStart = false; while (args.Length >= 1) { switch (args[0]) { default: goto Break; case "-profile": ProfileMode = true; ShiftArgs(ref args, 1); break; case "-showlog": showLogOnStart = true; ShiftArgs(ref args, 1); break; case "-swap": EnableSwap = true; ShiftArgs(ref args, 1); break; case "-noswap": EnableSwap = false; ShiftArgs(ref args, 1); break; } } Break: if (args.Length == 0) { // HACK: based on what WinMerge does - usability is suspect but still much better than FolderBrowserDialog. // See: http://www.codeproject.com/Articles/44914/Select-file-or-folder-from-the-same-dialog using (OpenFileDialog dialog = new OpenFileDialog()) { dialog.ValidateNames = false; dialog.CheckFileExists = false; dialog.CheckPathExists = false; dialog.Title = "Select a Folder"; dialog.FileName = "Select Folder."; if (dialog.ShowDialog() != DialogResult.OK) { return; } args = new string[] { Path.GetDirectoryName(dialog.FileName) }; } } if (args.Length != 1) { MessageBox.Show("Program must take one argument which is the path to a directory containing images to adjust."); return; } Window window = null; ImageCache cache = new ImageCache(); string directory = Path.GetFullPath(args[0]); string scanDirectory = GetScanDirectoryFromTargetDirectory(directory); if (!Directory.Exists(directory)) { MessageBox.Show(String.Format("Specified directory does not exist: \"{0}\"", directory)); return; } // load xml file XPathNavigator settingsNav; { XmlDocument settings = new XmlDocument(); string settingsPath = Path.Combine(scanDirectory, SettingsFile); if (File.Exists(settingsPath)) { settings.Load(settingsPath); } settingsNav = settings.CreateNavigator(); } GlobalOptions options = new GlobalOptions(settingsNav.SelectSingleNode("/*/options")); { using (GlobalOptionsDialog dialog = new GlobalOptionsDialog(options, directory)) { Application.Run(dialog); if (dialog.DialogResult != DialogResult.OK) { return; } } } if (showLogOnStart) { logWindow = new LoggingWindow(); logWindow.Show(); } // read saved items from xml List <Item> items = new List <Item>(); Dictionary <string, bool> filesWithItems = new Dictionary <string, bool>(); foreach (XPathNavigator itemNav in settingsNav.Select("/*/items/item")) { string fileName = itemNav.SelectSingleNode("file").Value; string filePath = Path.Combine(directory, fileName); if (File.Exists(filePath)) // drop item records for which file no longer exists { Item item = new Item(Path.Combine(directory, fileName), options, cache); item.ReadXml(itemNav); items.Add(item); filesWithItems[fileName.ToLowerInvariant()] = false; } } // add new records for any files that don't have a record foreach (string filePath in Directory.GetFiles(scanDirectory)) { string fileName = Path.GetFileName(filePath); if (String.Equals(fileName, SettingsFile, StringComparison.OrdinalIgnoreCase)) { continue; } if (!filesWithItems.ContainsKey(fileName.ToLowerInvariant())) { Item item = new Item(Path.Combine(directory, fileName), options, cache); items.Add(item); item.SettingsNav = settingsNav; // no match; try after hash has been computed } } // timestamp actions if (options.Timestamps.HasValue) { Parallel.ForEach( items, GetProcessorConstrainedParallelOptions2(CancellationToken.None), delegate(Item item) { if (options.Timestamps.Value) { if (!options.TimestampsOverwriteExisting) { DateTime existingTimestamp; if (TryParseFilenameTimestamp(Path.GetFileName(item.TargetPath), out existingTimestamp)) { return; } } DateTime fsCreated, fsModified, exifCreated; bool found = TryGetTimestamps( item.SourcePath, out fsCreated, out fsModified, out exifCreated); string created = fsCreated.ToString("s").Replace("T", "."); string modified = fsModified.ToString("s").Replace("T", "."); string exif = found ? exifCreated.ToString("s").Replace("T", ".") : "<no data>"; string newName = StripFilenameTimestamp(Path.GetFileName(item.TargetPath)); string text = FormatFilenameTimestamp( found ? exifCreated : (!options.TimestampsExifMissingModifiedInsteadOfCreated ? fsCreated : fsModified)); newName = String.Concat(text, " ", newName); item.RenamedFileName = newName; } else { item.RenamedFileName = StripFilenameTimestamp(Path.GetFileName(item.TargetPath)); } }); // ensure no duplicate names Dictionary <string, bool> usedName = new Dictionary <string, bool>(); foreach (Item item in items) { if (usedName.ContainsKey(item.RenamedFileName)) { int suffix = 0; while (true) { string newName = String.Concat(Path.GetFileNameWithoutExtension(item.RenamedFileName), "-", ++suffix, Path.GetExtension(item.RenamedFileName)); if (!usedName.ContainsKey(newName)) { item.RenamedFileName = newName; break; } } } usedName.Add(item.RenamedFileName, false); } } if (items.Count != 0) { window = new Window(directory, items, cache, options); window.Show(); window.LastAnalysisTask = BatchAnalyzerQueue.BeginAnalyzeBatch(items); Application.Run(window); } else { MessageBox.Show(String.Format("The specified folder \"{0}\" contains no images.", scanDirectory)); } LoggingWindow logw = logWindow; if (logw != null) { logw.Close(); } if (window != null) { window.LastAnalysisTask.Wait(); // allow any unfinished actions to cancel and dispose state } cache.Dispose(); SerializationManager.Manager.Dispose(); ImageClient.Close(); } catch (Exception exception) { Debugger.Log(0, null, exception.ToString()); MessageBox.Show(exception.ToString()); } }