Drawing a texture in a custom PropertyDrawer

I am trying to create a custom property drawer for a type that should be displayed as a 2D image, but when I use EditorGUI.DrawPreviewTexture or GUI.DrawTexture to draw the image, it disappears after a short time, and reappears for a short time when the inspector is updated. How do I properly draw a texture in a PropertyDrawer?

Minimal code:

[CustomPropertyDrawer (typeof (testObj))]
public class testObjDrawer : PropertyDrawer {
    int tSize = 128;
    Texture2D texture;
    
    public override void OnGUI(Rect pos, SerializedProperty prop, GUIContent label) {
        if (texture == null)
            InitializeTexture();
        EditorGUI.BeginProperty (pos, label, prop);
        pos = EditorGUI.PrefixLabel (pos, GUIUtility.GetControlID(FocusType.Passive), label);
        EditorGUI.DrawPreviewTexture(pos, texture);
        EditorGUI.EndProperty ();
    }
    
    private void InitializeTexture() {
        texture = new Texture2D(tSize, tSize,TextureFormat.RGB24,false);
        //tex = new Texture2D(tSize, tSize);
        Color32[] clr = new Color32[tSize * tSize];
        for (int i = 0; i < clr.Length; i++) 
            clr[i] = Color.black;
        texture.SetPixels32(clr);
        texture.Apply();
    }
}

I’m starting to think this is a bug, because it did the same thing when I tried a conversion of the EditorWindow example for EditorGUI.DrawPreviewTexture.
I did find a workaround involving GUIStyle

GUIStyle style = new GUIStyle();
style.normal.background = texture;
EditorGUI.LabelField(position, GUIContent.none, style);

I found out that the texture doesn’t flicker if the PropertyDrawer is drawn by a custom Editor. If you know where your PropertyDrawer is going to be used, simply make an empty stub deriving from the Editor for that object, like this:

[CustomEditor(typeof(ScriptableObject))]
public class ScriptableObjectInspector : Editor
{
	// Empty stub so a custom PropertyDrawer with textures would work.
	// (Workaround for Unity bug #514655.)
}

That’s the closest I got to drawing textures with custom texture coordinates (e.g. sprites).

However, remember to put your drawing calls behind:

if (Event.current.type == EventType.Repaint) {
	EditorGUI.DrawPreviewTexture(rect, texture);
}

Or you’ll get garbage in the supplied position rectangle. The documentation only mentions it for Graphics.DrawTexture(), but it is also needed in this case.

I had this issue and came up with a similar workaround to @MalikDrako except without allocating GUIStyle’s each time OnGUI events occur.

This aims to function as a drop-in replacement for GUI.DrawTexture:

public static class GUIHelper {

	private static GUIStyle s_TempStyle = new GUIStyle();

	public static void DrawTexture(Rect position, Texture2D texture) {
		if (Event.current.type != EventType.Repaint)
			return;

		s_TempStyle.normal.background = texture;

		s_TempStyle.Draw(position, GUIContent.none, false, false, false, false);
	}

}

Since 2017.3 you can override CanCacheInspectorGUI to fix this issue:

public override bool CanCacheInspectorGUI(SerializedProperty property)
{
    return false;
}

More details here.

Two alternatives to DrawPreviewTexture that don’t use GUIStyle and use the ScaleToFit scaling method: scale down the texture keeping the aspect ratio only when position is smaller than the texture rect.

// ScaleToFit method, left-aligned at the position of the prefix label
EditorGUI.LabelField(position, new GUIContent(texture));

// ScaleToFit method, horizontally centered, bottom-aligned, shadow effect
EditorGUI.DropShadowLabel(position, new GUIContent(texture));

I had similar issue but when using EditorGUI.DrawRect(). My workaround is to add this simple line somewhere in your script (like below your PropertyDrawer class):

[CanEditMultipleObjects][CustomEditor(typeof(MonoBehaviour),true)] public class MonoBehaviour_DummyCustomEditor : Editor {}//this line fixes EditorGUI flickering bug

ps: if your class doesn’t inherit from MonoBehaviour then just replace it with that class name (or it’s parent class if you are using inheritance)

This script copies the sprite to a new texture, not optimized but seems to work.
Caveat: The texture must be set as Read/Write Enabled in import settings.

public static class GUIHelper
{
    private static GUIStyle s_TempStyle = new GUIStyle();

    public static void DrawTexture(Rect position, Texture2D texture)
    {
        if (Event.current.type != EventType.Repaint)
            return;

        s_TempStyle.normal.background = texture;
        s_TempStyle.Draw(position, GUIContent.none, false, false, false, false);
    }

    public static void DrawTexture(Rect position, Sprite sprite)
    {
        if (Event.current.type != EventType.Repaint)
            return;

        //The texture must be set as Read/Write Enabled in import settings.

        Texture2D texture = new Texture2D((int)sprite.rect.width, (int)sprite.rect.height, sprite.texture.format, false);
        Color[] pixels = sprite.texture.GetPixels((int)sprite.rect.x, (int)sprite.rect.y, (int)sprite.rect.width, (int)sprite.rect.height);
        texture.SetPixels(pixels);
        texture.Apply();

        s_TempStyle.normal.background = texture;
        s_TempStyle.Draw(position, GUIContent.none, false, false, false, false);
    }

}

Unity: 5.6.1f1

Couldn’t get scriptableobject sprites to draw consistently in a property drawer. The default EditorGUI.ObjectField when large enough to show the icon would randomly flicker the sprite and render it with a delay after clicking in the inspector panel.

Bunch of helpful tips in this thread. In the end I changed the height of the ObjectField so the default icon is not drawn and use a GUIStyle to render the sprite as a background. Using this method I can mash the arrow key down through my scriptableobjects and the sprites show instantly with now weirdness in the inspector.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

[CustomPropertyDrawer(typeof(Sprite))]
public class SpriteDrawer : PropertyDrawer {

    private static GUIStyle s_TempStyle = new GUIStyle();
    
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {                                        
        var ident = EditorGUI.indentLevel;
        EditorGUI.indentLevel = 0;

        Rect spriteRect;
        
        //create object field for the sprite
        spriteRect = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
        property.objectReferenceValue = EditorGUI.ObjectField(spriteRect, property.name, property.objectReferenceValue, typeof(Sprite), false);

        //if this is not a repain or the property is null exit now
        if (Event.current.type != EventType.Repaint || property.objectReferenceValue == null)
            return;

        //draw a sprite
        Sprite sp = property.objectReferenceValue as Sprite;

        spriteRect.y += EditorGUIUtility.singleLineHeight+4;
        spriteRect.width = 64;
        spriteRect.height = 64;        
        s_TempStyle.normal.background = sp.texture;
        s_TempStyle.Draw(spriteRect, GUIContent.none, false, false, false, false);
                
        EditorGUI.indentLevel = ident;
    }

    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        return base.GetPropertyHeight(property, label) + 70f;
    }
}