// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.IO; // Directory. using System.Diagnostics; // Debug. using System.Drawing.Imaging; // ImageData using System.Runtime.InteropServices; // DllImport using System.Xml.Serialization; using System.Net.Mail; namespace ImageValidator { public partial class Form1 : Form, IMessageFilter { // ImageValidatorData data; // ImageValidatorIntermediate intermediate; // The column we are currently using for sorting. private ColumnHeader SortingColumn = null; // -1 if there is no drag private int MouseStartTrackOffsetX; private int MouseStartTrackOffsetY; private float ImageOffsetX; private float ImageOffsetY; // 0 means 1:1, 120 means down sample private int ImageZoom; // -------------------------------------------------------------------- // to redirect Mouse wheel message to the control under it, not the control in focus // from http://stackoverflow.com/questions/4769854/windows-forms-capturing-mousewheel public bool PreFilterMessage(ref Message m) { if (m.Msg == 0x20a) { // WM_MOUSEWHEEL, find the control at screen position m.LParam Point pos = new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16); IntPtr hWnd = WindowFromPoint(pos); if (hWnd != IntPtr.Zero && hWnd != m.HWnd && Control.FromHandle(hWnd) != null) { SendMessage(hWnd, m.Msg, m.WParam, m.LParam); return true; } } return false; } // P/Invoke declarations [DllImport("user32.dll")] private static extern IntPtr WindowFromPoint(Point pt); [DllImport("user32.dll")] private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp); // -------------------------------------------------------------------- public Form1() { MouseStartTrackOffsetX = -1; InitializeComponent(); Application.AddMessageFilter(this); } public void PopulateList() { intermediate = new ImageValidatorIntermediate(); data.PopulateList(textBoxTestDir.Text, textBoxRefDir.Text); // sort by column 1 SetListViewSorting(1); { EventArgs e = null; Form1_Resize(this, e); listView1_Click(this, e); } } private void Form1_Load(object sender, EventArgs e) { // textBoxRefDir.Text = @"F:\Martin.Mittring_Z3941_Dev_Rendering\Samples\Sandbox\RenderTest\Saved\Automation\RenderOutputValidation\CL12"; // textBoxTestDir.Text = @"F:\Martin.Mittring_Z3941_Dev_Rendering\Samples\Sandbox\RenderTest\Saved\Automation\RenderOutputValidation\CL13"; SetupFileSystemWatcher(); folderBrowserDialog1.RootFolder = Environment.SpecialFolder.MyComputer; folderBrowserDialog1.ShowNewFolderButton = false; // folderBrowserDialog1.SelectedPath = @"F:/Martin.Mittring_Z3941_Dev_Rendering/Samples/Sandbox/RenderTest/Saved/Automation/RenderOutputValidation/CL224"; listView1.AllowColumnReorder = true; listView1.FullRowSelect = true; listView1.GridLines = true; listView1.Columns.Add("Result (pixels failed)", 120, HorizontalAlignment.Left); listView1.Columns.Add("Matinee Location (in sec)", 140, HorizontalAlignment.Left); // listView1.Columns.Add("Actor", 100, HorizontalAlignment.Left); // listView1.Columns.Add("Map", 100, HorizontalAlignment.Left); // listView1.Columns.Add("Platform", 100, HorizontalAlignment.Left); listView1.Columns.Add("Folder Name", 120, HorizontalAlignment.Left); listView1.Columns.Add("File Name", 350, HorizontalAlignment.Left); // sort by second colunm { SortingColumn = listView1.Columns[1]; listView1.Columns[1].Text = "> " + listView1.Columns[1].Text; } saveFileDialog1.FileName = "Default.IVxml"; saveFileDialogExport.FileName = "ImageValidatorReport.html"; PopulateList(); // every 50ms we look for more work timer1.Interval = 50; timer1.Start(); // load example, if not there, clear like "New" LoadSettings("Default.IVxml"); } private int FindListViewIndexFromImageIndex(int ImageIndex) { int Ret = 0; foreach (ListViewItem item in this.listView1.Items) { if (item.ImageIndex == ImageIndex) { break; } ++Ret; } return Ret; } private void buttonPickRefFolder_Click(object sender, EventArgs e) { folderBrowserDialog1.SelectedPath = textBoxRefDir.Text; if (folderBrowserDialog1.ShowDialog() == DialogResult.OK) { textBoxRefDir.Text = folderBrowserDialog1.SelectedPath; SetupFileSystemWatcher(); PopulateList(); // todo: Update ListView? } } private void buttonPickTestFolder_Click(object sender, EventArgs e) { folderBrowserDialog1.SelectedPath = textBoxTestDir.Text; if (folderBrowserDialog1.ShowDialog() == DialogResult.OK) { textBoxTestDir.Text = folderBrowserDialog1.SelectedPath; SetupFileSystemWatcher(); PopulateList(); // todo: Update ListView? } } private void textBoxTestDir_KeyDown(object sender, KeyEventArgs e) { if (e.KeyValue == 13) { PopulateList(); SetupFileSystemWatcher(); // todo: Update ListView? } } private void textBoxRefDir_KeyDown(object sender, KeyEventArgs e) { if (e.KeyValue == 13) { PopulateList(); SetupFileSystemWatcher(); // todo: Update ListView? } } private void UpdateListView() { // select first element if no element is currently selected int SelectedImageIndex = 0; if (listView1.SelectedIndices.Count == 1) { SelectedImageIndex = listView1.Items[listView1.SelectedIndices[0]].ImageIndex; listView1.SelectedIndices.Clear(); } listView1.Items.Clear(); ImageValidatorSettings settings = GetSettings(); int Index = 0; foreach (ImageValidatorData.ImageEntry entry in data.imageEntries) { ImageValidatorData.ImageEntryColumnData columnData = new ImageValidatorData.ImageEntryColumnData(entry.Name); string ResultString = "?"; if (entry.testResult != null) { ResultString = entry.testResult.GetString(ref settings); } ListViewItem item = new ListViewItem(ResultString, Index++); item.SubItems.Add(columnData.Time); // item.SubItems.Add((1.0f + 0.1f * Index).ToString()); // item.SubItems.Add(columnData.Actor); // item.SubItems.Add(columnData.Map); // item.SubItems.Add(columnData.Platform); item.SubItems.Add(Path.GetDirectoryName(entry.Name)); item.SubItems.Add(Path.GetFileName(entry.Name)); if (entry.testResult != null) { item.BackColor = entry.testResult.GetColor(ref settings); } listView1.Items.Add(item); } if (listView1.Items.Count > 0) { listView1.SelectedIndices.Add(FindListViewIndexFromImageIndex(SelectedImageIndex)); } } // @return 0 if nothing is selected private int GetSelectedListViewIndex() { ListView.SelectedIndexCollection indexes = listView1.SelectedIndices; return (indexes.Count == 1) ? indexes[0] : 0; } // @return 0 if nothing is there private int GetSelectedImageEntryIndex() { int ListViewIndex = GetSelectedListViewIndex(); return listView1.Items.Count != 0 ? listView1.Items[ListViewIndex].ImageIndex : 0; } private ImageValidatorSettings GetSettings() { ImageValidatorSettings settings = new ImageValidatorSettings(); settings.TestDir = textBoxTestDir.Text; settings.RefDir = textBoxRefDir.Text; { int Value; Int32.TryParse(this.textBoxThreshold.Text, out Value); if (Value > 0) { settings.Threshold = (uint)Value; } } { int Value; Int32.TryParse(this.textBoxCountToFail.Text, out Value); if (Value > 0) { settings.PixelCountToFail = (uint)Value; } } /* if (settings.TestDir == settings.RefDir) { // it doesn't make sense to compare the same folders settings.TestDir = ""; settings.RefDir = ""; } */ return settings; } private void SetSettings(ImageValidatorSettings settings) { textBoxTestDir.Text = settings.TestDir; textBoxRefDir.Text = settings.RefDir; textBoxThreshold.Text = settings.Threshold.ToString(); textBoxCountToFail.Text = settings.PixelCountToFail.ToString(); } private void UpdateImages(int ImageEntryIndex) { if (data.imageEntries.Count != 0) { intermediate.Process(GetSettings(), data.imageEntries[ImageEntryIndex]); } pictureBoxTest.Invalidate(); pictureBoxDiff.Invalidate(); pictureBoxRef.Invalidate(); UpdateListView(); } private void listView1_Click(object sender, EventArgs e) { UpdateImages(GetSelectedImageEntryIndex()); Form1_Resize(sender, e); } private float ComputeScale() { System.Drawing.Size imageSize = intermediate.GetImagesSize(); if (imageSize.Width > 0 && imageSize.Height > 0) { return (float)Math.Pow(2, ImageZoom / 120) * pictureBoxTest.Width / (float)imageSize.Width; } else { return 1.0f; } } private void DrawOutlinedString(Graphics g, int x, int y, string drawString, Font drawFont) { SolidBrush outlineBrush = new SolidBrush(Color.Black); SolidBrush drawBrush = new SolidBrush(Color.White); const int r = 2; // bold black border around the text to make it more readable for (int dy = -r; dy <= r; ++dy) for (int dx = -r; dx <= r; ++dx) { if(dx != 0 && dy != 0) { g.DrawString(drawString, drawFont, outlineBrush, new PointF(x + dx, y + dy)); } } g.DrawString(drawString, drawFont, drawBrush, new PointF(x + 0, y + 0)); } private void PaintImage(Bitmap image, object sender, PaintEventArgs e, string drawString) { Graphics g = e.Graphics; PictureBox pictureBox = sender as PictureBox; if (image != null) { GraphicsUnit Unit = GraphicsUnit.Pixel; RectangleF bounds = image.GetBounds(ref Unit); float Scale = ComputeScale(); g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor; RectangleF destRect = new RectangleF(); destRect.Size = new SizeF(bounds.Width * Scale, bounds.Height * Scale); destRect.Location = new PointF(-ImageOffsetX * Scale, -ImageOffsetY * Scale); g.DrawImage(image, destRect, bounds, Unit); } Font drawFont = new Font("Arial", 12); SizeF textSize = g.MeasureString(drawString, drawFont); DrawOutlinedString(g, (int)(pictureBox.Width / 2 - textSize.Width / 2), (int)(pictureBox.Height - textSize.Height), drawString, drawFont); } private void pictureBoxTest_Paint(object sender, PaintEventArgs e) { PaintImage(intermediate.imageTest, sender, e, "Test"); } private void pictureBoxDiff_Paint(object sender, PaintEventArgs e) { PaintImage(intermediate.imageDiff, sender, e, "Difference"); } private void pictureBoxRef_Paint(object sender, PaintEventArgs e) { PaintImage(intermediate.imageRef, sender, e, "Reference"); } private void Form1_Resize(object sender, EventArgs e) { // better with try // http://stackoverflow.com/questions/16441643/how-to-avoid-screen-flickering // this.SuspendLayout(); int totalWidth = ClientSize.Width; int totalHeight = ClientSize.Height; int border = pictureBoxTest.Location.X; // 4 borders: left, right, between test and diff, between diff and ref int imagewidth = (totalWidth - 4 * border) / 3; if (imagewidth < 0) imagewidth = 0; System.Drawing.Size imageSize = intermediate.GetImagesSize(); // Y / X float AspectRatio = imageSize.Height / (float)imageSize.Width; int imageheight = (int)(imagewidth * AspectRatio); int x0 = border; int x1 = x0 + imagewidth + border; int x2 = totalWidth - imagewidth - border; int y = totalHeight - imageheight - border; pictureBoxTest.Location = new System.Drawing.Point(x0, y); pictureBoxTest.Size = new System.Drawing.Size(imagewidth, imageheight); pictureBoxDiff.Location = new System.Drawing.Point(x1, y); pictureBoxDiff.Size = new System.Drawing.Size(imagewidth, imageheight); pictureBoxRef.Location = new System.Drawing.Point(x2, y); pictureBoxRef.Size = new System.Drawing.Size(imagewidth, imageheight); int listHeight = y - listView1.Location.Y - border; if (listHeight < 0) listHeight = 0; listView1.Size = new System.Drawing.Size(totalWidth - 2 * border, listHeight); // this.ResumeLayout(); } private void textBoxCountToFail_TextChanged(object sender, EventArgs e) { UpdateListView(); } private void textBoxThreshold_TextChanged(object sender, EventArgs e) { PopulateList(); UpdateImages(GetSelectedImageEntryIndex()); } private void ClearListViewSorting() { if (SortingColumn != null) { // Remove the old sort indicator. SortingColumn.Text = SortingColumn.Text.Substring(2); SortingColumn = null; } } private void SetListViewSorting(int ColumnIndex) { ClearListViewSorting(); // sort by the specified column { ColumnClickEventArgs e = new ColumnClickEventArgs(ColumnIndex); listView1_ColumnClick(listView1, e); } } private void listView1_ColumnClick(object sender, ColumnClickEventArgs e) { // Sort on this column // from http://csharphelper.com/blog/2014/09/sort-a-listview-using-the-column-you-click-in-c // Get the new sorting column. ColumnHeader new_sorting_column = listView1.Columns[e.Column]; // Figure out the new sorting order. System.Windows.Forms.SortOrder sort_order; if (SortingColumn == null) { // New column. Sort ascending. sort_order = SortOrder.Ascending; } else { // See if this is the same column. if (new_sorting_column == SortingColumn) { // Same column. Switch the sort order. if (SortingColumn.Text.StartsWith("> ")) { sort_order = SortOrder.Descending; } else { sort_order = SortOrder.Ascending; } } else { // New column. Sort ascending. sort_order = SortOrder.Ascending; } ClearListViewSorting(); } // Display the new sort order. SortingColumn = new_sorting_column; if (sort_order == SortOrder.Ascending) { SortingColumn.Text = "> " + SortingColumn.Text; } else { SortingColumn.Text = "< " + SortingColumn.Text; } // Create a comparer listView1.ListViewItemSorter = new ListViewComparer(e.Column, sort_order); // Sort listView1.Sort(); } private void timer1_Tick(object sender, EventArgs e) { if (data.ProcessOneElement(GetSettings())) { UpdateListView(); } } private void SetupFileSystemWatcher() { // At the moment the feature is not stable so it's deactivated if(false) { ImageValidatorSettings settings = GetSettings(); try { fileSystemWatcher1.Path = settings.TestDir; // fileSystemWatcher1.NotifyFilter = NotifyFilters.LastWrite; fileSystemWatcher1.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; fileSystemWatcher2.Path = settings.RefDir; // fileSystemWatcher2.NotifyFilter = NotifyFilters.LastWrite; fileSystemWatcher2.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; } catch (System.ArgumentException) { } catch (System.IO.FileNotFoundException) { // no need to handle this } } } // from http://stackoverflow.com/questions/1406808/wait-for-file-to-be-freed-by-process/1406853#1406853 public static bool IsFileReady(String sFilename) { // If the file can be opened for exclusive access it means that the file // is no longer locked by another process. try { using (FileStream inputStream = File.Open(sFilename, FileMode.Open, FileAccess.Read, FileShare.None)) { if (inputStream.Length > 0) { return true; } else { return false; } } } catch (Exception) { return false; } } private void fileSystemWatcher1_Changed(object sender, FileSystemEventArgs e) { // Note: can hang, not sure why // wait for file to be finished (see bottom: http://stackoverflow.com/questions/15017506/using-filesystemwatcher-to-monitor-a-directory) while (!IsFileReady(e.FullPath)) { } { PopulateList(); SetupFileSystemWatcher(); // todo: Update ListView? } } private void fileSystemWatcher1_Renamed(object sender, RenamedEventArgs e) { FileSystemEventArgs args = new FileSystemEventArgs(e.ChangeType, e.FullPath, e.Name); fileSystemWatcher1_Changed(sender, args); } private void pictureBoxTest_MouseMove(object sender, MouseEventArgs e) { bool bWasDrag = MouseStartTrackOffsetX != -1; bool bDrag = (e.Button & MouseButtons.Left) != 0; float Scale = ComputeScale(); bool bUpdate = false; // usually 120, 0 or -120 int RelWheel = e.Delta; if (RelWheel != 0) { ImageOffsetX += e.X / Scale; ImageOffsetY += e.Y / Scale; ImageZoom += RelWheel; Scale = ComputeScale(); ImageOffsetX -= e.X / Scale; ImageOffsetY -= e.Y / Scale; if (ImageZoom <= 0) { ImageZoom = 0; ImageOffsetX = 0; ImageOffsetY = 0; } bUpdate = true; } if (bDrag && bWasDrag && ImageZoom > 0) { int RelX = e.X - MouseStartTrackOffsetX; int RelY = e.Y - MouseStartTrackOffsetY; // drag ImageOffsetX -= RelX / Scale; ImageOffsetY -= RelY / Scale; // clamp to border ImageOffsetX = Math.Max(0, ImageOffsetX); ImageOffsetY = Math.Max(0, ImageOffsetY); System.Drawing.Size imageSize = intermediate.GetImagesSize(); ImageOffsetX = Math.Min(imageSize.Width - pictureBoxTest.Width / Scale, ImageOffsetX); ImageOffsetY = Math.Min(imageSize.Height - pictureBoxTest.Height / Scale, ImageOffsetY); bUpdate = true; } if (bDrag) { MouseStartTrackOffsetX = e.X; MouseStartTrackOffsetY = e.Y; } else { // disable drag MouseStartTrackOffsetX = -1; MouseStartTrackOffsetY = 0; } if (bUpdate) { pictureBoxTest.Invalidate(); pictureBoxDiff.Invalidate(); pictureBoxRef.Invalidate(); pictureBoxTest.Update(); pictureBoxDiff.Update(); pictureBoxRef.Update(); } } private void newToolStripMenuNew_Click(object sender, EventArgs e) { DialogResult d = MessageBox.Show("Are you sure you want to create a new document?\nYour changes will not be saved.", "Question", MessageBoxButtons.YesNo, MessageBoxIcon.Question); if (d == DialogResult.Yes) { ImageValidatorSettings settings = new ImageValidatorSettings(); SetSettings(settings); PopulateList(); } } private bool SaveSettings(string FileName) { SetFileName(saveFileDialog1.FileName); ImageValidatorSettings settings = GetSettings(); XmlSerializer serializer = new XmlSerializer(settings.GetType()); try { using (StreamWriter file = new StreamWriter(FileName)) { serializer.Serialize(file, settings); } } catch (Exception) { return false; } return true; } // settings file name private string GetFileName() { return openFileDialog1.FileName; } // settings file name private void SetFileName(string FileName) { // set window title Text = Path.GetFileName(FileName) + " - ImageValidator " + ImageValidatorSettings.GetVersionString(); openFileDialog1.FileName = FileName; } private bool LoadSettings(string FileName) { SetFileName(FileName); ImageValidatorSettings settings = new ImageValidatorSettings(); XmlSerializer serializer = new XmlSerializer(settings.GetType()); try { using (StreamReader file = new StreamReader(FileName)) { settings = serializer.Deserialize(file) as ImageValidatorSettings; SetSettings(settings); } PopulateList(); SetupFileSystemWatcher(); } catch (Exception) { // update UI SetSettings(settings); return false; } // update UI SetSettings(settings); return true; } private void saveToolStripMenuSave_Click(object sender, EventArgs e) { if (!SaveSettings(saveFileDialog1.FileName)) { // show SaveAs dialog if needed saveAsToolStripMenuSaveAs_Click(sender, e); } } private void saveAsToolStripMenuSaveAs_Click(object sender, EventArgs e) { if (saveFileDialog1.ShowDialog() == DialogResult.OK) { SaveSettings(saveFileDialog1.FileName); } } private void quitToolStripMenuQuit_Click(object sender, EventArgs e) { DialogResult d = MessageBox.Show("Are you sure you want to quit?\nYour changes will not be saved.", "Question", MessageBoxButtons.YesNo, MessageBoxIcon.Question); if (d == DialogResult.Yes) { Application.Exit(); } } private void openToolStripMenuItem_Click(object sender, EventArgs e) { if (openFileDialog1.ShowDialog() == DialogResult.OK) { LoadSettings(GetFileName()); } } private void howToUseWithUnrealEngineToolStripMenuItem_Click(object sender, EventArgs e) { // todo: System.Diagnostics.Process.Start("http://www.google.com"); } /* // http://stackoverflow.com/questions/19134062/encode-a-filestream-to-base64-with-c-sharp public string ConvertToBase64(Stream stream) { byte[] inArray = new byte[(int)stream.Length]; stream.Read(inArray, 0, (int)stream.Length); // return Convert.ToBase64String(inArray); byte[] byt = System.Text.Encoding.UTF8.GetBytes(inArray); // convert the byte array to a Base64 string return Convert.ToBase64String(byt); } */ private void WaitUntilTestIsDone(ref ImageValidatorData data, ref ImageValidatorSettings settings) { // todo: progress bar, cancel button while (data.ProcessOneElement(settings)) { UpdateListView(); } } private void GenerateHTMLReport(bool bThumbnails) { // string f = Clipboard.GetText(); // HtmlFragment.CopyToClipboard("Hello!"); // HtmlFragment.CopyToClipboard("
Firstname | Lastname | Points |
---|---|---|
Jill | Smith | 50 |
Eve | Jackson | 94 |
John | Doe | 80 |