// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. using System; using System.IO; using System.Linq; using UnityEditor; using UnityEngine; namespace HoloToolkit.Unity { public class SetIconsWindow : EditorWindow { private const string InitialOutputDirectoryName = "TileGenerator"; private const float GUISectionOffset = 10.0f; private const string GUIHorizSpacer = " "; private const string EditorPrefsKey_AppIcon = "_EditorPrefsKey_AppIcon"; private const string EditorPrefsKey_SplashImage = "_EditorPrefsKey_SplashImage"; private const string EditorPrefsKey_DirectoryName = "_EditorPrefsKey_DirectoryName"; private static string _outputDirectoryName; private static string _originalAppIconPath; private static string _newAppIconPath; private static string _originalSplashImagePath; private static string _newSplashImagePath; private static Texture2D _originalAppIcon; private static Texture2D _originalSplashImage; private static float defaultLabelWidth; [MenuItem("Mixed Reality Toolkit/Tile Generator", false, 0)] private static void OpenWindow() { // Dock it next to the inspector. Type inspectorType = Type.GetType("UnityEditor.InspectorWindow,UnityEditor.dll"); var window = GetWindow<SetIconsWindow>(inspectorType); window.titleContent = new GUIContent("Tile Generator"); window.minSize = new Vector2(320, 256); window.Show(); } private void OnEnable() { // Load settings _originalAppIconPath = EditorPrefsUtility.GetEditorPref(EditorPrefsKey_AppIcon, _originalAppIconPath); _originalSplashImagePath = EditorPrefsUtility.GetEditorPref(EditorPrefsKey_SplashImage, _originalSplashImagePath); _outputDirectoryName = EditorPrefsUtility.GetEditorPref(EditorPrefsKey_DirectoryName, _outputDirectoryName); if (!string.IsNullOrEmpty(_originalAppIconPath)) { _originalAppIcon = AssetDatabase.LoadAssetAtPath<Texture2D>(_originalAppIconPath); } if (!string.IsNullOrEmpty(_originalSplashImagePath)) { _originalSplashImage = AssetDatabase.LoadAssetAtPath<Texture2D>(_originalSplashImagePath); } if (string.IsNullOrEmpty(_outputDirectoryName)) { _outputDirectoryName = Application.dataPath + "/" + InitialOutputDirectoryName; } defaultLabelWidth = EditorGUIUtility.labelWidth; } private void OnDisable() { SaveSettings(); } private void OnGUI() { GUILayout.Space(GUISectionOffset); // Images section GUILayout.Label("Images"); // Inputs for images _originalAppIcon = CreateImageInput("App Icon", 1240, 1240, _originalAppIcon, ref _originalAppIconPath); _originalSplashImage = CreateImageInput("Splash Image", 2480, 1200, _originalSplashImage, ref _originalSplashImagePath); if (GUILayout.Button("Choose Output Folder")) { _outputDirectoryName = EditorUtility.OpenFolderPanel("Output Folder", Application.dataPath, ""); } // Input for directory name EditorGUIUtility.labelWidth = 85f; EditorGUILayout.TextField("Output folder:", _outputDirectoryName); EditorGUIUtility.labelWidth = defaultLabelWidth; EditorGUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); // Update Icons if (GUILayout.Button("Update\nIcons", GUILayout.Height(64f), GUILayout.Width(64f))) { if (_originalAppIcon == null) { EditorUtility.DisplayDialog("App Icon not set", "Please select the App Icon first", "Ok"); } else if (_originalSplashImage == null) { EditorUtility.DisplayDialog("Splash Image not set", "Please select the Splash Image first", "Ok"); } else { EditorApplication.delayCall += ResizeImages; } } EditorGUILayout.EndHorizontal(); } private static void SaveSettings() { EditorPrefsUtility.SetEditorPref(EditorPrefsKey_AppIcon, _originalAppIconPath); EditorPrefsUtility.SetEditorPref(EditorPrefsKey_SplashImage, _originalSplashImagePath); EditorPrefsUtility.SetEditorPref(EditorPrefsKey_DirectoryName, _outputDirectoryName); } private static Texture2D CreateImageInput(string imageTitle, int width, int height, Texture2D texture, ref string path) { EditorGUIUtility.labelWidth = 200f; var newIcon = (Texture2D)EditorGUILayout.ObjectField(GUIHorizSpacer + imageTitle + " (" + width + "x" + height + ")", texture, typeof(Texture2D), false); EditorGUIUtility.labelWidth = defaultLabelWidth; if (newIcon == null || newIcon == texture) { return newIcon; } if (newIcon.width != width && newIcon.height != height) { // reset EditorUtility.DisplayDialog("Invalid Image", 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), "Ok"); newIcon = texture; } else { path = AssetDatabase.GetAssetPath(newIcon); } return newIcon; } private static void ResizeImages() { try { EditorUtility.DisplayProgressBar("Generating images", "Checking Texture Importers", 0); // Check if we need to reimport the original textures, for enabling reading. if (CheckTextureImporter(_originalAppIconPath) || CheckTextureImporter(_originalSplashImagePath)) { AssetDatabase.Refresh(); } if (!Directory.Exists(_outputDirectoryName)) { Directory.CreateDirectory(_outputDirectoryName); } else { foreach (string file in Directory.GetFiles(_outputDirectoryName)) { File.Delete(file); } } // Create a copy of the original images string outputDirectoryBasePath = _outputDirectoryName; outputDirectoryBasePath = outputDirectoryBasePath.Replace(Application.dataPath, "Assets"); _newAppIconPath = outputDirectoryBasePath + "/BaseIcon_1240x1240.png"; _newSplashImagePath = outputDirectoryBasePath + "/BaseSplashImage_2480x1200.png"; AssetDatabase.CopyAsset(_originalAppIconPath, _newAppIconPath); AssetDatabase.CopyAsset(_originalSplashImagePath, _newSplashImagePath); // Set Default Icon in Player Settings (Multiple platforms, can be overridden per platform) PlayerSettings.SetIconsForTargetGroup(BuildTargetGroup.Unknown, new[] { AssetDatabase.LoadAssetAtPath<Texture2D>(_newAppIconPath) }); PlayerSettings.virtualRealitySplashScreen = AssetDatabase.LoadAssetAtPath<Texture2D>(_newSplashImagePath); // Loop through available types and scales for UWP var types = Enum.GetValues(typeof(PlayerSettings.WSAImageType)).Cast<PlayerSettings.WSAImageType>().ToList(); var scales = Enum.GetValues(typeof(PlayerSettings.WSAImageScale)).Cast<PlayerSettings.WSAImageScale>().ToList(); float progressTotal = types.Count * scales.Count; float progress = 0; bool canceled = false; foreach (var type in types) { if (canceled) { break; } foreach (var scale in scales) { PlayerSettings.WSA.SetVisualAssetsImage(CloneAndResizeToFile(type, scale), type, scale); progress++; if (EditorUtility.DisplayCancelableProgressBar("Generating images", string.Format("Generating resized images {0} of {1}", progress, progressTotal), progress / progressTotal)) { canceled = true; break; } } } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate); if (canceled) { EditorUtility.DisplayDialog("Generation canceled", string.Format("{0} Images of {1} were resized and updated in the Player Settings.", progress, progressTotal), "Ok"); } else { EditorUtility.DisplayDialog("Images resized!", "All images were resized and updated in the Player Settings", "Ok"); } EditorUtility.ClearProgressBar(); } catch (Exception e) { Debug.LogError(e.Message); EditorUtility.ClearProgressBar(); } } private static bool CheckTextureImporter(string assetPath) { var tImporter = AssetImporter.GetAtPath(assetPath) as TextureImporter; if (tImporter == null || tImporter.isReadable) { return false; } tImporter.isReadable = true; AssetDatabase.ImportAsset(assetPath); return true; } private static string CloneAndResizeToFile(PlayerSettings.WSAImageType type, PlayerSettings.WSAImageScale scale) { string texturePath = GetUWPImageTypeTexture(type, scale); if (string.IsNullOrEmpty(texturePath)) { return string.Empty; } var iconSize = GetUWPImageTypeSize(type, scale); if (iconSize == Vector2.zero) { return string.Empty; } if (iconSize.x == 1240 && iconSize.y == 1240 || iconSize.x == 2480 && iconSize.y == 1200) { return texturePath; } string filePath = string.Format("{0}/{1}_AppIcon_{2}x{3}.png", _outputDirectoryName, Application.productName, iconSize.x, iconSize.y); filePath = filePath.Replace(Application.dataPath, "Assets"); if (File.Exists(filePath)) { return filePath; } // Create copy of original image try { AssetDatabase.CopyAsset(texturePath, filePath); var clone = AssetDatabase.LoadAssetAtPath<Texture2D>(filePath); if (clone == null) { Debug.LogError("Unable to load texture at " + filePath); return string.Empty; } // Resize clone to desired size TextureScale.Bilinear(clone, (int)iconSize.x, (int)iconSize.y); // Crop Color[] pix = clone.GetPixels(0, 0, (int)iconSize.x, (int)iconSize.y); clone = new Texture2D((int)iconSize.x, (int)iconSize.y, TextureFormat.ARGB32, false); clone.SetPixels(pix); clone.Apply(); var rawData = clone.EncodeToPNG(); File.WriteAllBytes(filePath, rawData); if (rawData.Length > 204800) { Debug.LogWarningFormat("{0} exceeds the minimum file size of 204,800 bytes, please use a smaller image for generating your icons.", filePath); } } catch (Exception e) { Debug.LogError(e.Message); return string.Empty; } return filePath; } private static string GetUWPImageTypeTexture(PlayerSettings.WSAImageType type, PlayerSettings.WSAImageScale scale) { switch (type) { case PlayerSettings.WSAImageType.PackageLogo: case PlayerSettings.WSAImageType.UWPSquare44x44Logo: case PlayerSettings.WSAImageType.UWPSquare71x71Logo: case PlayerSettings.WSAImageType.UWPSquare150x150Logo: case PlayerSettings.WSAImageType.UWPSquare310x310Logo: return _newAppIconPath; case PlayerSettings.WSAImageType.SplashScreenImage: case PlayerSettings.WSAImageType.UWPWide310x150Logo: if (scale != PlayerSettings.WSAImageScale.Target16 && scale != PlayerSettings.WSAImageScale.Target24 && scale != PlayerSettings.WSAImageScale.Target32 && scale != PlayerSettings.WSAImageScale.Target48 && scale != PlayerSettings.WSAImageScale.Target256) { return _newSplashImagePath; } else { return _newAppIconPath; } default: throw new ArgumentOutOfRangeException("type", type, null); } } private static Vector2 GetUWPImageTypeSize(PlayerSettings.WSAImageType type, PlayerSettings.WSAImageScale scale) { switch (scale) { case PlayerSettings.WSAImageScale.Target16: return CreateSquareSize(16); case PlayerSettings.WSAImageScale.Target24: return CreateSquareSize(24); case PlayerSettings.WSAImageScale.Target32: return CreateSquareSize(32); case PlayerSettings.WSAImageScale.Target48: return CreateSquareSize(48); case PlayerSettings.WSAImageScale.Target256: return CreateSquareSize(256); default: return GetWSAImageTypeSize(type, scale); } } private static Vector2 GetWSAImageTypeSize(PlayerSettings.WSAImageType type, PlayerSettings.WSAImageScale scale) { float scaleFactor = float.Parse(scale.ToString().Replace("_", "")) * 0.01f; switch (type) { case PlayerSettings.WSAImageType.PackageLogo: return CreateSquareSize(50, scaleFactor); case PlayerSettings.WSAImageType.UWPSquare44x44Logo: return CreateSquareSize(44, scaleFactor); case PlayerSettings.WSAImageType.UWPSquare71x71Logo: return CreateSquareSize(71, scaleFactor); case PlayerSettings.WSAImageType.UWPSquare150x150Logo: return CreateSquareSize(150, scaleFactor); case PlayerSettings.WSAImageType.UWPSquare310x310Logo: return CreateSquareSize(310, scaleFactor); // WIDE 31:15 case PlayerSettings.WSAImageType.UWPWide310x150Logo: return CreateSize(new Vector2(310, 150), scaleFactor); case PlayerSettings.WSAImageType.SplashScreenImage: return CreateSize(new Vector2(620, 300), scaleFactor); default: Debug.LogWarningFormat("Invalid image size for {0} with scale {1}X{2}", type, scale, scaleFactor); return Vector2.zero; } } private static Vector2 CreateSquareSize(int size, float scaleFactor = 1f) { var newSize = new Vector2(size * scaleFactor, size * scaleFactor); newSize.x = (float)Math.Ceiling(newSize.x); newSize.y = (float)Math.Ceiling(newSize.y); return newSize; } private static Vector2 CreateSize(Vector2 size, float scaleFactor = 1f) { var newSize = new Vector2(size.x * scaleFactor, size.y * scaleFactor); newSize.x = (float)Math.Ceiling(newSize.x); newSize.y = (float)Math.Ceiling(newSize.y); return newSize; } } }