Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
ImageComparator.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.CodeDom.Compiler;
5 using System.Diagnostics;
6 using System.IO;
7 using System.Text;
8 using System.Text.RegularExpressions;
9 using SiliconStudio.ImageComparerService;
10 
11 namespace SiliconStudio.Paradox.Graphics.Regression
12 {
14  {
15  #region Private static members
16 
17  /// <summary>
18  /// A object to prevent concurrent json writing.
19  /// </summary>
20  private static readonly object WriterLocker = new object();
21 
22  /// <summary>
23  /// Image Magick bin directory.
24  /// </summary>
25  private static string imageMagickDir;
26 
27  /// <summary>
28  /// The directory where the gold images are.
29  /// </summary>
30  private const string GoldFolder = @"gold\";
31 
32  /// <summary>
33  /// The directory where the json files should be saved.
34  /// </summary>
35  private const string JsonFolder = @"json\";
36 
37  /// <summary>
38  /// The directory where to save the generated images.
39  /// </summary>
40  private const string BuildFolder = @"build\";
41 
42  #endregion
43 
44  /// <summary>
45  /// A flag to enable the json file writing.
46  /// </summary>
47  public bool SaveJson;
48 
49  public ImageComparator()
50  {
51  imageMagickDir = System.Environment.GetEnvironmentVariable("IMAGEMAGICK_DIR");
52  if (imageMagickDir == null)
53  imageMagickDir = Environment.CurrentDirectory;
54 
55  SaveJson = false;
56  }
57 
58  /// <summary>
59  /// Performs the comparison between the generated image and its base.
60  /// </summary>
61  /// <param name="receivedImage">The received image.</param>
62  /// <returns></returns>
63  public bool Compare_RMSE(TestResultServerImage receivedImage, string resultTempFileName)
64  {
65  bool copyOnShare = (receivedImage.Client.Connection.Flags & ImageComparisonFlags.CopyOnShare) != 0;
66 
67  var testPerformed = false;
68 
69  // Test if gold file exists (if not, create it)
70  if (!File.Exists(receivedImage.GoldFileName))
71  {
72  Console.WriteLine(@"Generating a new gold image.");
73  File.Delete(receivedImage.ResultFileName); // remove the old version of the result file, if it already exists.
74  File.Copy(resultTempFileName, receivedImage.ResultFileName);
75  File.Copy(resultTempFileName, receivedImage.GoldFileName);
76  receivedImage.MeanSquareError = 0; // we don't want a error on request status after creating a new gold image
77  }
78  else
79  {
80  var tempGoldFileName = Path.GetTempPath() + Guid.NewGuid() + ".png";
81  var tempDiffFileName = Path.GetTempPath() + Guid.NewGuid() + ".png";
82 
83  try
84  {
85  File.Copy(receivedImage.GoldFileName, tempGoldFileName);
86 
87  Console.WriteLine(@"Gold image found. Performing tests.");
88  var output = RunImageMagickCommand(
89  @"compare",
90  string.Format(
91  "-metric rmse \"{0}\" \"{1}\" \"{2}\"",
92  tempGoldFileName,
93  resultTempFileName,
94  tempDiffFileName));
95 
96  var outputResult = Regex.Match(output.OutputErrors[0], @"(\S*) \((\S*)\)");
97  var mse = 0.0f;
98  if (!float.TryParse(outputResult.Groups[1].Value, out mse))
99  {
100  var stringBuild = new StringBuilder();
101  stringBuild.Append("Errors:\n");
102  foreach (var err in output.OutputErrors)
103  {
104  stringBuild.Append(" ");
105  stringBuild.Append(err);
106  stringBuild.Append("\n");
107  }
108  stringBuild.Append("Messages:\n");
109  foreach (var mess in output.OutputLines)
110  {
111  stringBuild.Append(" ");
112  stringBuild.Append(mess);
113  stringBuild.Append("\n");
114  }
115 
116  Console.WriteLine(@"Unable to read the value of the error between the two images.");
117  Console.WriteLine(stringBuild.ToString());
118  }
119  else
120  {
121  Console.WriteLine(@"Error: " + mse);
122 
123  //copy files to server
124  if (mse != 0.0f)
125  {
126  Console.WriteLine(@"Existing difference with gold image. Continuing tests.");
127 
128  // visually compute difference
129  RunImageMagickCommand(
130  @"composite",
131  string.Format(
132  "\"{0}\" \"{1}\" -compose difference \"{2}\"",
133  tempGoldFileName,
134  resultTempFileName,
135  tempDiffFileName));
136 
137  // normalize the output
138  RunImageMagickCommand(
139  @"convert",
140  string.Format(
141  "\"{0}\" -auto-level \"{1}\"",
142  tempDiffFileName,
143  receivedImage.DiffNormFileName));
144 
145  File.Copy(resultTempFileName, receivedImage.ResultFileName, true);
146  File.Copy(tempDiffFileName, receivedImage.DiffFileName, true);
147  }
148  else
149  {
150  Console.WriteLine(@"No difference with gold image.");
151 
152  // Delete diff image (that might have been generated by previous run)
153  File.Delete(receivedImage.DiffFileName);
154  File.Delete(receivedImage.DiffNormFileName);
155  }
156 
157  receivedImage.MeanSquareError = mse;
158 
159  if (SaveJson)
160  WriteJson(receivedImage);
161 
162  testPerformed = true;
163  }
164  }
165  catch (Exception ex)
166  {
167  Console.WriteLine(@"An exception occured in ImageMagick.");
168  Console.WriteLine(ex);
169  }
170  finally
171  {
172  File.Delete(tempDiffFileName);
173  File.Delete(tempGoldFileName);
174  }
175  }
176  return testPerformed;
177  }
178 
179  #region Private methods
180 
181  /// <summary>
182  /// Run an imageMagick shell command.
183  /// </summary>
184  /// <param name="commandName">The name of the command.</param>
185  /// <param name="commandParameters">The arguments of the command.</param>
186  /// <returns>The output of the command.</returns>
187  private static ProcessOutputs RunImageMagickCommand(string commandName, string commandParameters)
188  {
189  return ShellHelper.RunProcessAndGetOutput(Path.Combine(imageMagickDir, commandName), commandParameters);
190  }
191 
192  /// <summary>
193  /// Get or Create the Json file for this build.
194  /// </summary>
195  /// <param name="receivedImage">The received image information.</param>
196  /// <returns>The file stream.</returns>
197  private Stream GetJsonFileStream(TestResultServerImage receivedImage)
198  {
199  var fileName = GetJsonFileName(receivedImage);
200  bool fileExists = File.Exists(fileName);
201  var stream = File.Open(fileName, FileMode.OpenOrCreate, FileAccess.Write);
202 
203  if (fileExists)
204  {
205  stream.Seek(-1, SeekOrigin.End);
206  WriteInStream(",", stream);
207  }
208  else
209  {
210  WriteInStream("[", stream);
211  }
212  return stream;
213  }
214 
215  #endregion
216 
217  #region Static methods
218 
219  /// <summary>
220  /// Write a string in the stream.
221  /// </summary>
222  /// <param name="name">The string to write.</param>
223  /// <param name="stream">The stream to write into.</param>
224  public static void WriteInStream(string name, Stream stream)
225  {
226  stream.Write(Encoding.ASCII.GetBytes(name), 0, Encoding.ASCII.GetByteCount(name));
227  }
228 
229  /// <summary>
230  /// Get the name of the json for this test.
231  /// </summary>
232  /// <param name="receivedImage">The received image information.</param>
233  /// <returns>The name of the json file.</returns>
234  private static string GetJsonFileName(TestResultServerImage receivedImage)
235  {
236  return Path.Combine(receivedImage.JsonPath, receivedImage.GetJsonFileName());
237  }
238 
239  #endregion
240 
241  /// <summary>
242  /// Add the image to the json.
243  /// </summary>
244  /// <param name="receivedImage">The image to add.</param>
245  /// <param name="baseImage">The address of the base image.</param>
246  private void WriteJson(TestResultServerImage receivedImage)
247  {
248  lock (WriterLocker)
249  {
250  using (var stream = GetJsonFileStream(receivedImage))
251  {
252  var newEntry = receivedImage.GetJsonString();
253  WriteInStream(newEntry, stream);
254  WriteInStream("]", stream);
255  }
256  }
257  }
258  }
259 }
bool Compare_RMSE(TestResultServerImage receivedImage, string resultTempFileName)
Performs the comparison between the generated image and its base.
System.Text.Encoding Encoding
System.IO.File File
bool SaveJson
A flag to enable the json file writing.
static void WriteInStream(string name, Stream stream)
Write a string in the stream.