Search Unity

Non-intrusive textures and icons

Discussion in 'Editor Workflows' started by Catsoft-Studios, Dec 11, 2020.

  1. Catsoft-Studios

    Catsoft-Studios

    Joined:
    Jan 15, 2011
    Posts:
    703
    Hi! I wanted to share the approach I'm currently taking to include Editor textures for components in the Inspector and Gizmos.

    Motivation

    What's driving my motivation is that I'm a tool-scripting Asset Store developer, so all textures included in my packages are not supposed to be used by the end-user.

    Screenshot 2020-12-11 at 09.52.14.png

    If I include textures as standard objects, these can be seen in the floating window of any Texture object picker, which pollutes their project and messes their organisation.

    Our approach

    So, instead of dropping in a PNG texture inside the Unity folder and using the path to that texture in the Editor script, I create a
    byte[]
    array that contains the raw data of the PNG and reconstruct it whenever it's needed, using the
    Texture2D.LoadRawTextureData
    .

    Screenshot 2020-12-11 at 09.32.06.png

    Then, I can easily create a script that inherits from a custom class called TextureRaw and use it to cache the values in a static dictionary, in case they are later needed.

    Code (CSharp):
    1. public abstract class TextureRaw
    2. {
    3.     private static readonly Dictionary<int, Texture2D> Cache = new Dictionary<int, Texture2D>();
    4.  
    5.     protected abstract byte[] Raw { get; }
    6.  
    7.     public Texture2D Texture
    8.     {
    9.         get
    10.         {
    11.             // uses the Cache dictionary to check if the texture has been loaded.
    12.             // If so, return the cached value. If not, load it from Raw, save
    13.             // it in the Cache dictionary using its hashcode as the key and return it.
    14.             // Note: So far hashcode doesn't give collisions, but I guess it could
    15.             // return a false collision. This may need some further thought.
    16.         }
    17.     }
    18. }
    Once we have this class defined, it's very easy to define a new texture. For example, let's say we want to display a small "chevron" icon for our custom Inspector. We can create a new class ChevronTexture that inherits from TextureRaw and fill the Raw field with the de raw data obtained using the Texture2D.GetRawTextureData(...) method.

    Code (CSharp):
    1. public class ChevronTexture : TextureRaw
    2. {
    3.     protected override Raw = new byte[]
    4.     {
    5.         0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
    6.         0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
    7.         0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
    8.         0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
    9.         0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
    10.         0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
    11.         0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
    12.         ...
    13.     };
    14. }
    Now, any time I need to use the Chevron icon in my custom scripts, all I need to do is to reference a ChevronTextue instance and get the Texture property:

    Code (CSharp):
    1. public class MyScriptEditor : UnityEditor.Editor
    2. {
    3.     private static readonly ChevronTexture chevron = new ChevronTexture();
    4.  
    5.     public override VisualElement CreateInspectorGUI()
    6.     {
    7.         Image myImage = new Image()
    8.         {
    9.             image = chevron.Texture // <--- this is where the magic happens
    10.         };
    11.  
    12.         Label label = new Label("Hello World");
    13.         VisualElement container = new VisualElement();
    14.  
    15.         container.Add(image);
    16.         container.Add(label);
    17.  
    18.         return container;
    19.     }
    20. }
    Further thoughts

    Once I had this built I quickly realised that I could easily feed a white texture as the source image and tint it with the color I needed for different cases, allowing me to have a wide variety of colors using the same texture source.

    In order to save the different cached textures inside the
    Cache
    dictionary with different colors, all I need to do is to create a key that, instead of being the hashcode of the texture, it's the XOR of the hascode of the texture and the hashcode of the color. This returns a unique (note to self: Does it? There might be collisions) but predictable key that can be used to store the texture inside the dictionary.

    Screenshot 2020-12-11 at 09.33.23.png

    Moreover, and this is a bit our of the scope of what I wanted to share, but one could even create layers of textures that have different colors and generate a new texture. For example, you could have a "Mouse" texture as well as an "Arrow pointing to the right" texture and an "Arrow pointing to the left" texture.

    With these, one could easily create new "textures" by overlaying them one on top of the other, with different colors. There's a bit of black magic that allows to set the outline of the arrow as transparent so there's a clear distinction between the lower and the upper texture.

    Screenshot 2020-12-11 at 09.31.55.png

    One last thought

    While writing this, it has occurred to me that it would be even better if I created an editor script that would generate the new texture class by having a custom [ContextMenuItem] attached to the PNG texture. That way, in order to create new textures all I would need to do is to drop in the texture I want in Unity, right click it, choose "Convert to Texture Script" (need to think of a better name :p ) and it could automatically generate the script based on the name of the selected texture and raw data. I could then delete the PNG, as it's no longer needed.

    Last thought, now for real

    I remember reading Lazlo from Ludiq did something similar for Bolt. Though I believe he compiled all textures inside the Resources of the DLL. To be honest, I have no idea how that works or how then these can be accessed. If anyone has a different idea or want to further discuss this, I'm all ears!
     
    Last edited: Dec 11, 2020
    bricevdm, NotaNaN, Qriva and 4 others like this.
  2. Ruchir

    Ruchir

    Joined:
    May 26, 2015
    Posts:
    934
    Can you post one full example including how you did the outline?
     
    Catsoft-Studios likes this.
  3. Catsoft-Studios

    Catsoft-Studios

    Joined:
    Jan 15, 2011
    Posts:
    703
    A complete example might be too much, as there's a lot of boilerplate code. However, to make the cutout of the outline, here's what I did:

    As you may know, all textures have 3 channels: R, G and B (4 if you have alpha). We only need one, as all textures are white-colored and the tint is later applied. This is good news, because I can use Red (R) channel as the alpha mask and the G as the cutout mask. Here's how it looks like:

    Texture.png

    I've put each channel separately below. As you can see, now we can place the red channel on top of the texture and set the overall alpha channel of the resulting mask to transparent based on the Green channel.

    Here's the code excerpt I used for this:

    Some notes: WIDTH and HEIGHT are constants, because I always use a fixed icon size of 64x64, but these could very well be dynamic values. The overlay/watermark should be scaled accordingly if so.

    COLOR_WATERMARK is a Color value. In my case it's a cyan/blue which tone's a bit different depending whether I'm in darkmode or lightmode. This is the color of the overlay put on top of the texture (the arrow).

    Code (CSharp):
    1. Texture2D overlayTexture = this.m_Overlay?.Texture;
    2.  
    3. // Create a new texture based on the raw byte[] array
    4. texture = new Texture2D(WIDTH, HEIGHT, FORMAT, false);
    5. texture.LoadRawTextureData(this.Raw);
    6.  
    7. for (int i = 0; i < WIDTH; ++i)
    8. {
    9.     for (int j = 0; j < HEIGHT; ++j)
    10.     {
    11.         Color pixel = texture.GetPixel(i, j);
    12.         Color color = new Color(
    13.             pixel.r * this.m_Tint.r,
    14.             pixel.g * this.m_Tint.g,
    15.             pixel.b * this.m_Tint.b,
    16.             pixel.a
    17.         );
    18.  
    19.         // if there is an overlay/watermark texture, place it on top of the
    20.         // texture and cutout the border:
    21.         if (overlayTexture != null)
    22.         {
    23.             Color watermark = overlayTexture.GetPixel(i, j);
    24.             // Red channel works like the alpha channel:
    25.             color = Color.Lerp(color, COLOR_WATERMARK, 1f - watermark.r);
    26.  
    27.             // cutout the Green parts because these are the borders of the
    28.             // watermark/overlay texture:
    29.             color.a *= 1f - watermark.g;
    30.         }
    31.        
    32.         texture.SetPixel(i, j, color);
    33.     }
    34. }
    35.  
    36. texture.Apply();
    Hope this helps! After running this code, the resulting texture should be sent to the Cache dictionary aforementioned above. In order to save different textures with different colors, the hash okey of the dictionary should be calculated as:

    Code (CSharp):
    1. // XOR between the hash of the texture and the hash of the color tint:
    2. int hashKey = this.GetType().GetHashCode() ^ this.m_Tint.GetHashCode();
    3. // if the texture has an overlay/watermark, apply another XOR with the watermark's hashcode
    4. if (this.m_Overlay != null) hashKey ^= this.m_Overlay.GetHashCode();
     
    mariandev and Ruchir like this.
  4. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    Unity does the same thing internally in the Tilemap Extras package, kinda:

    Code (csharp):
    1. private const string s_XIconString = "iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuNWWFMmUAAABoSURBVDhPnY3BDcAgDAOZhS14dP1O0x2C/LBEgiNSHvfwyZabmV0jZRUpq2zi6f0DJwdcQOEdwwDLypF0zHLMa9+NQRxkQ+ACOT2STVw/q8eY1346ZlE54sYAhVhSDrjwFymrSFnD2gTZpls2OvFUHAAAAABJRU5ErkJggg==";
    2.  
    3. ...
    4.  
    5. s_Arrows[9] = Base64ToTexture(s_XIconString);
    6.  
    7. ...
    8.  
    9. /// <summary>
    10. /// Converts a Base64 string to a Texture2D
    11. /// </summary>
    12. /// <param name="base64">Base64 string containing image data</param>
    13. /// <returns>Texture2D containing an image from the given Base64 string</returns>
    14. public static Texture2D Base64ToTexture(string base64)
    15. {
    16.     Texture2D t = new Texture2D(1, 1);
    17.     t.hideFlags = HideFlags.HideAndDontSave;
    18.     t.LoadImage(System.Convert.FromBase64String(base64));
    19.     return t;
    20. }
    It is very convenient to be able to just skip the whole "where do I stuff this .png file in order to show UI things" problem. I've not got any clue if the byte-array or base64string is the better one.
     
    mariandev and Catsoft-Studios like this.
  5. Catsoft-Studios

    Catsoft-Studios

    Joined:
    Jan 15, 2011
    Posts:
    703
    Interesting! I have no idea either whether using a raw byte[] array is better or worse than Base64. I just used the byte[] because it allowed to keep the array's columns aligned
     
  6. awesomedata

    awesomedata

    Joined:
    Oct 8, 2014
    Posts:
    1,419
    Bringing this back from the dead, as @gabrielw_unity has not responded since this was created -- just wondering if a general-purpose Unity solution (i.e. with tooling / overlays) could use a method like this internally (and wrap it up into an API or UI tool for users who don't want to manage all the boilerplate this requires -- just to make the UX textures less intrusive to users for custom EditorTools and Overlays -- i.e. purchased from the AssetStore).

    Thanks for this topic, @Catsoft-Studios. This is a great option for people who care about their UX. :)
     
  7. Catsoft-Studios

    Catsoft-Studios

    Joined:
    Jan 15, 2011
    Posts:
    703
    Not sure if there's anything officially announced, but I just noted there's a new IconAttribute class. EDIT: It's not new, but moved from internal to public. This is due to Overlays having the option to specify an icon using the [Icon(...)] attribute.

    The decompiled documentation states: Attribute to specify an icon for a MonoBehaviour or ScriptableObject. For now it's a string path, I guess in the future this could be updated to support a
    byte[]
    or base64 string
     
    Last edited: May 18, 2021
    awesomedata and raphick like this.
  8. awesomedata

    awesomedata

    Joined:
    Oct 8, 2014
    Posts:
    1,419
    Interesting -- I wonder if Unity is internally going to convert the icon to base64 or a byte array?

    Might be worth it to talk to Graph Tools Foundation guys or perhaps the new Overlay guys about this one. I'd be interested in knowing if this is the solution to hiding editor icons.
     
    Catsoft-Studios likes this.
  9. We should bribe the Rider/VS/VSCode teams to create a path for just drop a PNG into the code editor and insert them as a Base64 string. (I like Base64 string better, it's more compact. In case of icons I don't care about the data structure, I won't change it in the editor, more important to not to take all the space, especially when it placed at the top of the file...)


    Edit: just realized, JetBrains has a plugin for it:
    https://plugins.jetbrains.com/plugin/8263-base64-image-encoder
     
    Last edited by a moderator: Jun 5, 2021
  10. Saberhagen_Industries

    Saberhagen_Industries

    Joined:
    Nov 6, 2012
    Posts:
    10
    Hello there o/ I am currently making a tool and did stumble upon this, it would certainly be easier to have a solution that unity provides for this like effective hiding of certain texture flags and so on... but I did also find this technique interesting so I gave it a shot. Worked like a charm using Texture2D.LoadRawTextureData to read Textures I rendered with a camera into a byte[] on a scriptable objects.

    I also have a bunch of Icons so I gave the Idea a shot about making an automatic conversion as a menu on Texture2D. I ran into a lot of trouble there when it comes to actually having my script write a new script and pasting in the byte[], I simply wasn't able to get anything usable to paste at all. I ended up using Base64 because that string was easy to paste. I however ran into trouble using LoadRawTextureData, Base64 only worked with EncodeToPNG(), something that has more limitations because you have to turn Read/Write on and turn off compression for a Texture to convert it. Any Idea how to properly paste LoadRawTextureData's byte[] as string via code? Or any idea why only the byte[] provided by EncodeToPNG worked for Base64?

    Now I needed a different set of icons and ran into yet another problem. Custom Cursors! You see those have to be specially configured.. they need to be RGBA32 ... easy enough and Read/Write enabled... which is kind of bad since apparently you cannot create a Texture2D with that enabled without making a file... and even when writing a file its not that trivial to set that up. Any ideas for a workaround that would make any of these techniques work for custom cursors? It feels like the efforts are kind of wasted when after all the converting there is still a couple of these textures showing up for the project :p
     
    awesomedata likes this.
  11. sand_lantern

    sand_lantern

    Joined:
    Sep 15, 2017
    Posts:
    210
    So, I have something that seems to be working, it feels pretty hacky but it's better than nothing. Not getting a bunch of warnings logged in console at least.

    Code (CSharp):
    1. public static Texture2D BuildMouseTexture(string base64data){
    2.     Texture2D tempSource = new Texture2D (32, 32, TextureFormat.RGBA32, false);
    3.     tempSource.alphaIsTransparency = true;
    4.     tempSource.hideFlags = HideFlags.HideAndDontSave;
    5.     tempSource.LoadImage (System.Convert.FromBase64String (base64data), false);
    6.     tempSource.Apply (updateMipmaps: false, makeNoLongerReadable: false);
    7.  
    8.     RenderTexture rend = RenderTexture.GetTemporary (32, 32, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear);
    9.     Graphics.Blit (tempSource, rend);
    10.     RenderTexture previous = RenderTexture.active;
    11.     RenderTexture.active = rend;
    12.  
    13.     Texture2D texture = new Texture2D (tempSource.width, tempSource.height, TextureFormat.RGBA32, false);
    14.     texture.ReadPixels (new Rect (0, 0, rend.width, rend.height), 0, 0, false);
    15.     texture.alphaIsTransparency = true;
    16.     texture.hideFlags = HideFlags.HideAndDontSave;
    17.     texture.Apply (updateMipmaps: false);
    18.  
    19.     RenderTexture.active = previous;
    20.     RenderTexture.ReleaseTemporary (rend);
    21.     UnityEngine.Object.DestroyImmediate (tempSource);
    22.  
    23.     return texture;
    24. }
    It was inspired by this answer I found on Stack Overflow

    Basically in order to have it be readable, you need to create a render texture. The only way I can figure out how to make a render texture is to feed it a Texture2D. So I create a temporary Texture2D, feed it into a temporary render texture, then use that to create another Texture2D that is marked as readable.

    I'd be curious if anyone can see anything with this that is gonna come back and bite me, but it seems to be a solution. Not a good solution but a solution.
     
    Last edited: Feb 11, 2022
  12. AKhodakarami

    AKhodakarami

    Joined:
    Feb 27, 2022
    Posts:
    12
    Hi, I know this is not a new thread, but I just read the whole thing, i am not new to unity but, with my experience, when someone more experienced like you, have a nice idea like this, it would really be useful if you would help the community with a little tool (please make it free) and do exactly what you are saying here. The best thing would be if you make the tool as a dll plugin, this way, everyone would have a little plugin utility that will give them the api and the conversion tool to go in and do exactly the designing section of their own development without the need to reinventing the tool over and over.

    Thinking about the time gap between when you first sent this post and now, chances are that you have already developed such a tool for all of your projects, if you put a little time and effort to disect this tool from the rest and make a dll plugin out of it, you will have a modular tool that you can share with community and also you can use in every tool you build from now on. personally, I would really apreciate such an effort
     
  13. Catsoft-Studios

    Catsoft-Studios

    Joined:
    Jan 15, 2011
    Posts:
    703
    I lack the time to spend in creating a free tool and maintain it. However, I've gone quite in-depth in the first 2 posts about creating it, so it's not like you would have to start from scratch.

    My my...
     
    awesomedata and sand_lantern like this.
  14. AKhodakarami

    AKhodakarami

    Joined:
    Feb 27, 2022
    Posts:
    12
    I know creation of a tool is time consuming, but, being a free tool with base structure are you talk in first post, I don't think you would really need to maintain it, maybe if you put it as open source repository, people would contribute with push requests and that would be it, even if you don't do that much, the base should be solid enough to not need much maintenance?
     
  15. AKhodakarami

    AKhodakarami

    Joined:
    Feb 27, 2022
    Posts:
    12
    also, I'm not that familiar with bit shift and bit operations, how would you regain the color from this hashing code?
     
  16. awesomedata

    awesomedata

    Joined:
    Oct 8, 2014
    Posts:
    1,419
    There you go. That's it!

    Continue the "effort" you're exerting here, and a tool will soon appear magically out of thin-air for you to share with the community!


    And since you're so inquisitive (and are already working hard), let me share a secret with you:

    The first step in ensuring that tool will magically pop into existence for you is not by asking someone more skilled than you to create it "for everyone", but in actually figuring out the problems preventing its creation yourself -- and then doing that work yourself!


    Because, at best, people will not just ignore you or think you're lazy when you ask specific questions about solving some of these problems like the one above -- because they see you're actually trying and making some progress. People _like_ progress and want to _help_ with progress. But people with more skills and knowledge than you often don't have time or the motivation to go all the way with someone else's project or hold their hand the whole time, so they rarely want to start something they can't follow-through with, or bother helping someone else who can't bring their project the rest of the way themselves either.

    And even if you don't manage to bring your project all the way -- you have learned a valuable lesson about what you can and cannot do. You can either overcome these limitations, or move on and accept them as just a part of who you are. Either way isn't bad. One just requires more energy/effort than the other.
    But "effort" is really the big secret here. Asking others to exert effort for you is just sabotaging yourself and your own goals and dreams in life.

    It's like paying a gardener to plant crops for you to survive an apocalypse before the apocalypse happens, but then the money you used to use to pay him/her to maintain it eventually loses its value, and the gardener leaves to plant his own garden, meaning you must now learn how to do that work yourself from scratch -- or die. You could have watched and learned everything he did when he was here, but you didn't. You were lazy or ignorant and just didn't understand. However, you have one advantage still: You have a living garden to work from already. Just use the seeds from the aging plants he already gave you (i.e. the code and bitshifting concepts here) and grow your own stuff. Over time, you'll learn what the problems are in growing that kind of stuff (i.e. how to keep bugs and other creatures out without pesticides, since it's the apocalypse -- or, less metaphorically, how to handle bits and bytes to do what you want to do with them and leverage the API calls you learned with other similar problems in the future) and now maybe even thrive with this newfound knowledge and experience you've worked tirelessly for. At this point, what you do with this knowledge, whether you give it away freely or not, is now yours to decide. But you'll never have to worry about growing a garden (or making a tool like this) again -- or anything even remotely like it -- because you'll be able to transfer all those skills and that newfound sense of knowledge and experience to anything even remotely similar (i.e. pest-like thieves randomly attacking your home because you have food during the apocalypse). Keeping pests away in the apocalypse is a problem that affects people on all scales -- from tiny pests to human thieves with guns. In the case of thieves with guns, just as it is in the case of smaller pests, detracting them or making them disinterested in your stuff is paramount to your success. But you already know this -- because you've already lost crops and went hungry (i.e. asked and even had to dig for an answer to your coding problem and couldn't find it), and then tried again to prevent yourself from starving (by persisting to finally find the right API calls you needed and making it eventually work). So with a lot of luck and effort, you did manage to figure out the secret of exerting effort and persisting no mater what -- and survived this far. This is the secret. You have learned that you can do it once, so you know you can definitely do it again -- even if the pests are larger and have guns now. The concept is still the same and as applicable as it was before. Even if the techniques are different in how you actually implement them, the methodology is still the same: detract and deter the pests (no matter how many legs they have).


    Work smarter and not harder -- sure -- but don't believe others aren't smart enough to see through you when you're trying to get them to do your work for you -- for free. :/

    Game development is considered "hard" for a reason. Don't ever forget that you are going to have to work for almost everything in the end. You can't escape this fact in life. Yes, there are free tools and code and tips and tricks out there (it isn't the apocalypse _yet_), but don't get comfortable. Those are only a temporary respite, and can even cause more trouble than doing stuff yourself. You will find that often with free solutions, you'll have to do this anyway. It is often true that you get what you pay for. So pay for this upfront -- with your own time and effort.
     
    Last edited: May 20, 2022
  17. maldewar

    maldewar

    Joined:
    Dec 21, 2016
    Posts:
    52
    I've stumbled upon this post while searching for an answer to get a cleaner option to include GUI textures on my assets.
    I'd like to share my approach in case anyone finds it useful.

    I'm placing all GUI related textures inside an MyExtension/Editor/Resources/GUI/ folder, but changing the .png extension to either .logo or .ico. Using this the ObjectPicker dialog does not display these resources as textures.

    I have a singleton GUITextureManager class that loads all these textures from an Assets/ file path for each texture. I'm using an enum to identify each icon or logo, so I can cache these textures in a static dictionary to be consumed by the UI elements.

    Code (CSharp):
    1.  
    2.         /// <summary>
    3.         /// Loads a texture given its GUIElement enum value.
    4.         /// </summary>
    5.         /// <returns>Loaded texture or null if not found.</returns>
    6.         /// <param name="path">Path to texture file (begining at the Assets/ folder, including the file extension).</param>
    7.         /// <param name="guiElement">GUI element enumerator.</param>
    8.         private static Texture2D LoadTexture (string path, GUIElement guiElement) {
    9.             Texture2D texture = null;
    10.             #if UNITY_EDITOR
    11.             path = ExtensionManager.resourcesPath + path;
    12.             texture = UnityEditor.AssetDatabase.LoadAssetAtPath<Texture2D> (path);
    13.             // Trying to read a raw png.
    14.             if (texture == null && File.Exists(path)) {
    15.                 byte[] textureData = File.ReadAllBytes(path);
    16.                 texture = new Texture2D(2, 2);
    17.                 texture.LoadImage(textureData);
    18.             }
    19.             if (loadedTextures.ContainsKey (guiElement)) {
    20.                 if (loadedTextures [guiElement] != null) {
    21.                     Texture2D.DestroyImmediate (loadedTextures [guiElement], true);
    22.                 }
    23.                 loadedTextures.Remove (guiElement);
    24.             }
    25.             loadedTextures.Add (guiElement, texture);
    26.             #endif
    27.             return texture;
    28.         }
    29.  
    For example, this code will take a path like this and load it to a Texture2D if the file is found:
    Assets/Waldemarst/Broccoli/Editor/Resources/GUI/broccoli_logo.logo

    You can still open these files with a file viewer or an image editor (as these rely on metadata and not file extension to detect the file type), and the Unity Texture dialog will not display them as textures.
     
    Catsoft-Studios likes this.