AR Design
UBC EML collab with UBC SALA - visualizing IoT data in AR
SetIconsWindow.cs
Go to the documentation of this file.
1 // Copyright (c) Microsoft Corporation. All rights reserved.
2 // Licensed under the MIT License. See LICENSE in the project root for license information.
3 
4 using System;
5 using System.IO;
6 using System.Linq;
7 using UnityEditor;
8 using UnityEngine;
9 
10 namespace HoloToolkit.Unity
11 {
12  public class SetIconsWindow : EditorWindow
13  {
14  private const string InitialOutputDirectoryName = "TileGenerator";
15  private const float GUISectionOffset = 10.0f;
16  private const string GUIHorizSpacer = " ";
17  private const string EditorPrefsKey_AppIcon = "_EditorPrefsKey_AppIcon";
18  private const string EditorPrefsKey_SplashImage = "_EditorPrefsKey_SplashImage";
19  private const string EditorPrefsKey_DirectoryName = "_EditorPrefsKey_DirectoryName";
20 
21  private static string _outputDirectoryName;
22  private static string _originalAppIconPath;
23  private static string _newAppIconPath;
24  private static string _originalSplashImagePath;
25  private static string _newSplashImagePath;
26  private static Texture2D _originalAppIcon;
27  private static Texture2D _originalSplashImage;
28  private static float defaultLabelWidth;
29 
30  [MenuItem("Mixed Reality Toolkit/Tile Generator", false, 9)]
31  private static void OpenWindow()
32  {
33  // Dock it next to the inspector.
34  Type inspectorType = Type.GetType("UnityEditor.InspectorWindow,UnityEditor.dll");
35  var window = GetWindow<SetIconsWindow>(inspectorType);
36  window.titleContent = new GUIContent("Tile Generator");
37  window.minSize = new Vector2(320, 256);
38  window.Show();
39  }
40 
41  private void OnEnable()
42  {
43  // Load settings
44  _originalAppIconPath = EditorPrefsUtility.GetEditorPref(EditorPrefsKey_AppIcon, _originalAppIconPath);
45  _originalSplashImagePath = EditorPrefsUtility.GetEditorPref(EditorPrefsKey_SplashImage, _originalSplashImagePath);
46  _outputDirectoryName = EditorPrefsUtility.GetEditorPref(EditorPrefsKey_DirectoryName, _outputDirectoryName);
47 
48  if (!string.IsNullOrEmpty(_originalAppIconPath))
49  {
50  _originalAppIcon = AssetDatabase.LoadAssetAtPath<Texture2D>(_originalAppIconPath);
51  }
52 
53  if (!string.IsNullOrEmpty(_originalSplashImagePath))
54  {
55  _originalSplashImage = AssetDatabase.LoadAssetAtPath<Texture2D>(_originalSplashImagePath);
56  }
57 
58  if (string.IsNullOrEmpty(_outputDirectoryName))
59  {
60  _outputDirectoryName = Application.dataPath + "/" + InitialOutputDirectoryName;
61  }
62 
63  defaultLabelWidth = EditorGUIUtility.labelWidth;
64  }
65 
66  private void OnDisable()
67  {
68  SaveSettings();
69  }
70 
71  private void OnGUI()
72  {
73  GUILayout.Space(GUISectionOffset);
74 
75  // Images section
76  GUILayout.Label("Images");
77 
78  // Inputs for images
79  _originalAppIcon = CreateImageInput("App Icon", 1240, 1240, _originalAppIcon, ref _originalAppIconPath);
80  _originalSplashImage = CreateImageInput("Splash Image", 2480, 1200, _originalSplashImage, ref _originalSplashImagePath);
81 
82  if (GUILayout.Button("Choose Output Folder"))
83  {
84  _outputDirectoryName = EditorUtility.OpenFolderPanel("Output Folder", Application.dataPath, "");
85  }
86 
87  // Input for directory name
88  EditorGUIUtility.labelWidth = 85f;
89  EditorGUILayout.TextField("Output folder:", _outputDirectoryName);
90  EditorGUIUtility.labelWidth = defaultLabelWidth;
91 
92  EditorGUILayout.BeginHorizontal();
93  GUILayout.FlexibleSpace();
94  // Update Icons
95  if (GUILayout.Button("Update\nIcons", GUILayout.Height(64f), GUILayout.Width(64f)))
96  {
97  if (_originalAppIcon == null)
98  {
99  EditorUtility.DisplayDialog("App Icon not set", "Please select the App Icon first", "Ok");
100  }
101  else if (_originalSplashImage == null)
102  {
103  EditorUtility.DisplayDialog("Splash Image not set", "Please select the Splash Image first", "Ok");
104  }
105  else
106  {
107  EditorApplication.delayCall += ResizeImages;
108  }
109  }
110 
111  EditorGUILayout.EndHorizontal();
112  }
113 
114  private static void SaveSettings()
115  {
116  EditorPrefsUtility.SetEditorPref(EditorPrefsKey_AppIcon, _originalAppIconPath);
117  EditorPrefsUtility.SetEditorPref(EditorPrefsKey_SplashImage, _originalSplashImagePath);
118  EditorPrefsUtility.SetEditorPref(EditorPrefsKey_DirectoryName, _outputDirectoryName);
119  }
120 
121  private static Texture2D CreateImageInput(string imageTitle, int width, int height, Texture2D texture, ref string path)
122  {
123  EditorGUIUtility.labelWidth = 200f;
124  var newIcon = (Texture2D)EditorGUILayout.ObjectField(GUIHorizSpacer + imageTitle + " (" + width + "x" + height + ")", texture, typeof(Texture2D), false);
125  EditorGUIUtility.labelWidth = defaultLabelWidth;
126 
127  if (newIcon == null || newIcon == texture) { return newIcon; }
128 
129  if (newIcon.width != width && newIcon.height != height)
130  {
131  // reset
132  EditorUtility.DisplayDialog("Invalid Image",
133  string.Format("{0} should be an image with preferred size of {1}x{2}. Provided image was: {3}x{4}.", imageTitle, width, height, newIcon.width, newIcon.height),
134  "Ok");
135  newIcon = texture;
136  }
137  else
138  {
139  path = AssetDatabase.GetAssetPath(newIcon);
140  }
141 
142  return newIcon;
143  }
144 
145  private static void ResizeImages()
146  {
147  try
148  {
149  EditorUtility.DisplayProgressBar("Generating images", "Checking Texture Importers", 0);
150 
151  // Check if we need to reimport the original textures, for enabling reading.
152  if (CheckTextureImporter(_originalAppIconPath) || CheckTextureImporter(_originalSplashImagePath))
153  {
154  AssetDatabase.Refresh();
155  }
156 
157  if (!Directory.Exists(_outputDirectoryName))
158  {
159  Directory.CreateDirectory(_outputDirectoryName);
160  }
161  else
162  {
163  foreach (string file in Directory.GetFiles(_outputDirectoryName))
164  {
165  File.Delete(file);
166  }
167  }
168 
169  // Create a copy of the original images
170  string outputDirectoryBasePath = _outputDirectoryName;
171  outputDirectoryBasePath = outputDirectoryBasePath.Replace(Application.dataPath, "Assets");
172 
173  _newAppIconPath = outputDirectoryBasePath + "/BaseIcon_1240x1240.png";
174  _newSplashImagePath = outputDirectoryBasePath + "/BaseSplashImage_2480x1200.png";
175 
176  AssetDatabase.CopyAsset(_originalAppIconPath, _newAppIconPath);
177  AssetDatabase.CopyAsset(_originalSplashImagePath, _newSplashImagePath);
178 
179  // Set Default Icon in Player Settings (Multiple platforms, can be overridden per platform)
180  PlayerSettings.SetIconsForTargetGroup(BuildTargetGroup.Unknown, new[] { AssetDatabase.LoadAssetAtPath<Texture2D>(_newAppIconPath) });
181  PlayerSettings.virtualRealitySplashScreen = AssetDatabase.LoadAssetAtPath<Texture2D>(_newSplashImagePath);
182 
183  // Loop through available types and scales for UWP
184  var types = Enum.GetValues(typeof(PlayerSettings.WSAImageType)).Cast<PlayerSettings.WSAImageType>().ToList();
185  var scales = Enum.GetValues(typeof(PlayerSettings.WSAImageScale)).Cast<PlayerSettings.WSAImageScale>().ToList();
186  float progressTotal = types.Count * scales.Count;
187  float progress = 0;
188  bool canceled = false;
189 
190  foreach (var type in types)
191  {
192  if (canceled)
193  {
194  break;
195  }
196 
197  foreach (var scale in scales)
198  {
199  PlayerSettings.WSA.SetVisualAssetsImage(CloneAndResizeToFile(type, scale), type, scale);
200 
201  progress++;
202  if (EditorUtility.DisplayCancelableProgressBar("Generating images", string.Format("Generating resized images {0} of {1}", progress, progressTotal), progress / progressTotal))
203  {
204  canceled = true;
205  break;
206  }
207  }
208  }
209 
210  AssetDatabase.SaveAssets();
211  AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
212 
213  if (canceled)
214  {
215  EditorUtility.DisplayDialog("Generation canceled",
216  string.Format("{0} Images of {1} were resized and updated in the Player Settings.", progress, progressTotal),
217  "Ok");
218  }
219  else
220  {
221  EditorUtility.DisplayDialog("Images resized!",
222  "All images were resized and updated in the Player Settings",
223  "Ok");
224  }
225 
226  EditorUtility.ClearProgressBar();
227  }
228  catch (Exception e)
229  {
230  Debug.LogError(e.Message);
231  EditorUtility.ClearProgressBar();
232  }
233  }
234 
235  private static bool CheckTextureImporter(string assetPath)
236  {
237  var tImporter = AssetImporter.GetAtPath(assetPath) as TextureImporter;
238 
239  if (tImporter == null || tImporter.isReadable) { return false; }
240 
241  tImporter.isReadable = true;
242 
243  AssetDatabase.ImportAsset(assetPath);
244  return true;
245  }
246 
247  private static string CloneAndResizeToFile(PlayerSettings.WSAImageType type, PlayerSettings.WSAImageScale scale)
248  {
249  string texturePath = GetUWPImageTypeTexture(type, scale);
250 
251  if (string.IsNullOrEmpty(texturePath)) { return string.Empty; }
252 
253  var iconSize = GetUWPImageTypeSize(type, scale);
254 
255  if (iconSize == Vector2.zero) { return string.Empty; }
256 
257  if (iconSize.x == 1240 && iconSize.y == 1240 || iconSize.x == 2480 && iconSize.y == 1200) { return texturePath; }
258 
259  string filePath = string.Format("{0}/{1}_AppIcon_{2}x{3}.png", _outputDirectoryName, Application.productName, iconSize.x, iconSize.y);
260  filePath = filePath.Replace(Application.dataPath, "Assets");
261 
262  if (File.Exists(filePath))
263  {
264  return filePath;
265  }
266 
267  // Create copy of original image
268  try
269  {
270  AssetDatabase.CopyAsset(texturePath, filePath);
271  var clone = AssetDatabase.LoadAssetAtPath<Texture2D>(filePath);
272 
273  if (clone == null)
274  {
275  Debug.LogError("Unable to load texture at " + filePath);
276  return string.Empty;
277  }
278 
279  // Resize clone to desired size
280  TextureScale.Bilinear(clone, (int)iconSize.x, (int)iconSize.y);
281 
282  // Crop
283  Color[] pix = clone.GetPixels(0, 0, (int)iconSize.x, (int)iconSize.y);
284  clone = new Texture2D((int)iconSize.x, (int)iconSize.y, TextureFormat.ARGB32, false);
285  clone.SetPixels(pix);
286  clone.Apply();
287 
288  var rawData = clone.EncodeToPNG();
289  File.WriteAllBytes(filePath, rawData);
290 
291  if (rawData.Length > 204800)
292  {
293  Debug.LogWarningFormat("{0} exceeds the minimum file size of 204,800 bytes, please use a smaller image for generating your icons.", filePath);
294  }
295  }
296  catch (Exception e)
297  {
298  Debug.LogError(e.Message);
299  return string.Empty;
300  }
301 
302  return filePath;
303  }
304 
305  private static string GetUWPImageTypeTexture(PlayerSettings.WSAImageType type, PlayerSettings.WSAImageScale scale)
306  {
307  switch (type)
308  {
309  case PlayerSettings.WSAImageType.PackageLogo:
310  case PlayerSettings.WSAImageType.UWPSquare44x44Logo:
311  case PlayerSettings.WSAImageType.UWPSquare71x71Logo:
312  case PlayerSettings.WSAImageType.UWPSquare150x150Logo:
313  case PlayerSettings.WSAImageType.UWPSquare310x310Logo:
314  return _newAppIconPath;
315  case PlayerSettings.WSAImageType.SplashScreenImage:
316  case PlayerSettings.WSAImageType.UWPWide310x150Logo:
317  if (scale != PlayerSettings.WSAImageScale.Target16 &&
318  scale != PlayerSettings.WSAImageScale.Target24 &&
319  scale != PlayerSettings.WSAImageScale.Target32 &&
320  scale != PlayerSettings.WSAImageScale.Target48 &&
321  scale != PlayerSettings.WSAImageScale.Target256)
322  {
323  return _newSplashImagePath;
324  }
325  else
326  {
327  return _newAppIconPath;
328  }
329  default:
330  throw new ArgumentOutOfRangeException("type", type, null);
331  }
332  }
333 
334  private static Vector2 GetUWPImageTypeSize(PlayerSettings.WSAImageType type, PlayerSettings.WSAImageScale scale)
335  {
336  switch (scale)
337  {
338  case PlayerSettings.WSAImageScale.Target16:
339  return CreateSquareSize(16);
340  case PlayerSettings.WSAImageScale.Target24:
341  return CreateSquareSize(24);
342  case PlayerSettings.WSAImageScale.Target32:
343  return CreateSquareSize(32);
344  case PlayerSettings.WSAImageScale.Target48:
345  return CreateSquareSize(48);
346  case PlayerSettings.WSAImageScale.Target256:
347  return CreateSquareSize(256);
348  default:
349  return GetWSAImageTypeSize(type, scale);
350  }
351  }
352 
353  private static Vector2 GetWSAImageTypeSize(PlayerSettings.WSAImageType type, PlayerSettings.WSAImageScale scale)
354  {
355  float scaleFactor = float.Parse(scale.ToString().Replace("_", "")) * 0.01f;
356 
357  switch (type)
358  {
359  case PlayerSettings.WSAImageType.PackageLogo:
360  return CreateSquareSize(50, scaleFactor);
361  case PlayerSettings.WSAImageType.UWPSquare44x44Logo:
362  return CreateSquareSize(44, scaleFactor);
363  case PlayerSettings.WSAImageType.UWPSquare71x71Logo:
364  return CreateSquareSize(71, scaleFactor);
365  case PlayerSettings.WSAImageType.UWPSquare150x150Logo:
366  return CreateSquareSize(150, scaleFactor);
367  case PlayerSettings.WSAImageType.UWPSquare310x310Logo:
368  return CreateSquareSize(310, scaleFactor);
369 
370  // WIDE 31:15
371  case PlayerSettings.WSAImageType.UWPWide310x150Logo:
372  return CreateSize(new Vector2(310, 150), scaleFactor);
373  case PlayerSettings.WSAImageType.SplashScreenImage:
374  return CreateSize(new Vector2(620, 300), scaleFactor);
375  default:
376  Debug.LogWarningFormat("Invalid image size for {0} with scale {1}X{2}", type, scale, scaleFactor);
377  return Vector2.zero;
378  }
379  }
380 
381  private static Vector2 CreateSquareSize(int size, float scaleFactor = 1f)
382  {
383  var newSize = new Vector2(size * scaleFactor, size * scaleFactor);
384  newSize.x = (float)Math.Ceiling(newSize.x);
385  newSize.y = (float)Math.Ceiling(newSize.y);
386  return newSize;
387  }
388 
389  private static Vector2 CreateSize(Vector2 size, float scaleFactor = 1f)
390  {
391  var newSize = new Vector2(size.x * scaleFactor, size.y * scaleFactor);
392  newSize.x = (float)Math.Ceiling(newSize.x);
393  newSize.y = (float)Math.Ceiling(newSize.y);
394  return newSize;
395  }
396  }
397 }
static void SetEditorPref(string key, string value)
static void Bilinear(Texture2D tex, int newWidth, int newHeight)
Definition: TextureScale.cs:39
static string GetEditorPref(string key, string defaultValue)