Generating sprites dynamically from PNG or JPEG files in C#

Hey everyone!

I was searching the forums here (and some external sources) to find a way to load an external PNG file into a Unity-Sprite and display it on a UI.image element. It took me about a day to figure out how to do this! :-o

But i finally managed to do it and thought its about time to give back something to the forum and share this little class with you.

Hope this helps in any way in case you are looking for the same info as I did! :slight_smile:

using UnityEngine;
using System.Collections;
using System.IO;

public class IMG2Sprite : MonoBehaviour {

   // This script loads a PNG or JPEG image from disk and returns it as a Sprite
   // Drop it on any GameObject/Camera in your scene (singleton implementation)
   //
   // Usage from any other script:
   // MySprite = IMG2Sprite.instance.LoadNewSprite(FilePath, [PixelsPerUnit (optional)])

   private static IMG2Sprite _instance;

   public static IMG2Sprite instance
   {
     get     
     {
       //If _instance hasn't been set yet, we grab it from the scene!
       //This will only happen the first time this reference is used.

       if(_instance == null)
         _instance = GameObject.FindObjectOfType<IMG2Sprite>();
       return _instance;
     }
   }

   public Sprite LoadNewSprite(string FilePath, float PixelsPerUnit = 100.0f) {
   
     // Load a PNG or JPG image from disk to a Texture2D, assign this texture to a new sprite and return its reference
     
     Sprite NewSprite = new Sprite();
     Texture2D SpriteTexture = LoadTexture(FilePath);
     NewSprite = Sprite.Create(SpriteTexture, new Rect(0, 0, SpriteTexture.width, SpriteTexture.height),new Vector2(0,0), PixelsPerUnit);

     return NewSprite;
   }

   public Texture2D LoadTexture(string FilePath) {

     // Load a PNG or JPG file from disk to a Texture2D
     // Returns null if load fails

     Texture2D Tex2D;
     byte[] FileData;

     if (File.Exists(FilePath)){
       FileData = File.ReadAllBytes(FilePath);
       Tex2D = new Texture2D(2, 2);           // Create new "empty" texture
       if (Tex2D.LoadImage(FileData))           // Load the imagedata into the texture (size is set automatically)
         return Tex2D;                 // If data = readable -> return texture
     }   
     return null;                     // Return null if load failed
   }
}

Happy Coding!

46 Likes

Many, many, many thanks!

1 Like

just fyi, LoadImage has a big “thunk” to it to decode either PNG or JPG for textures… you should really look into using native formats for the images like DDS or PVR and use LoadRawTextureData (with their respective headers removed from the bits).

Thanks it helped a lot!

I found it much more efficient (for my use at least) to drop it into a static tools class and be able to reference it anywhere without having to drop it in the scene constantly. I also added SpriteMeshType so that you can choose (optional) whether to use Tight or FullRect. Unity likes to load FullRect faster than Tight so it might be use to some people for testing or real world use purposes. Just drop this into any script file and it will work outright :slight_smile:

Edit 2/9/2018: I added a simple conversion method to the IMG2Sprite class since I needed to convert Texture2D’s that are already loaded.
Edit 4/11/2019: Updated the sprite creation method to avoid the new Sprite() error.

using UnityEngine;
using System.Collections;
using System.IO;

public static class IMG2Sprite
{

    //Static class instead of _instance
    // Usage from any other script:
    // MySprite = IMG2Sprite.LoadNewSprite(FilePath, [PixelsPerUnit (optional)], [spriteType(optional)])

    public static Sprite LoadNewSprite(string FilePath, float PixelsPerUnit = 100.0f, SpriteMeshType spriteType = SpriteMeshType.Tight)
    {

        // Load a PNG or JPG image from disk to a Texture2D, assign this texture to a new sprite and return its reference

        Texture2D SpriteTexture = LoadTexture(FilePath);
        Sprite  NewSprite = Sprite.Create(SpriteTexture, new Rect(0, 0, SpriteTexture.width, SpriteTexture.height), new Vector2(0, 0), PixelsPerUnit, 0 , spriteType);

        return NewSprite;
    }

    public static Sprite ConvertTextureToSprite(Texture2D texture, float PixelsPerUnit = 100.0f, SpriteMeshType spriteType = SpriteMeshType.Tight)
    {
        // Converts a Texture2D to a sprite, assign this texture to a new sprite and return its reference

        Sprite NewSprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0, 0), PixelsPerUnit, 0, spriteType);

        return NewSprite;
    }

    public static Texture2D LoadTexture(string FilePath)
    {

        // Load a PNG or JPG file from disk to a Texture2D
        // Returns null if load fails

        Texture2D Tex2D;
        byte[] FileData;

        if (File.Exists(FilePath))
        {
            FileData = File.ReadAllBytes(FilePath);
            Tex2D = new Texture2D(2, 2);           // Create new "empty" texture
            if (Tex2D.LoadImage(FileData))           // Load the imagedata into the texture (size is set automatically)
                return Tex2D;                 // If data = readable -> return texture
        }
        return null;                     // Return null if load failed
    }
}
16 Likes

You can also use Resources.Load to create a sprite, if you have the image file in your Resources folder:

Sprite sprite = Resources.Load<Sprite>(path);```

This is handy if you have a collection of images, and want to be able to choose one dynamically by filename at runtime.
1 Like

can you show me how to use the script ?

Hey, Great script. Only problem I’m having is that my image is off centered (up and to the right) and I cannot get it so the center of image is in the middle of the sprite render. Any thought

—Solved—
Added a .5f, .5f pivot

There was an error message in VS2017 editor of the script
Sprite NewSprite = new Sprite();
Sprite doesnt contain a constructor that takes 0 arguments

Change:

public static Sprite LoadNewSprite(string FilePath, float PixelsPerUnit = 100.0f, SpriteMeshType spriteType = SpriteMeshType.Tight)
    {
        // Load a PNG or JPG image from disk to a Texture2D, assign this texture to a new sprite and return its reference
        Sprite NewSprite = new Sprite();
        Texture2D SpriteTexture = LoadTexture(FilePath);
        NewSprite = Sprite.Create(SpriteTexture, new Rect(0, 0, SpriteTexture.width, SpriteTexture.height), new Vector2(0, 0), PixelsPerUnit, 0 , spriteType);
        return NewSprite;
    }

to

public static Sprite LoadNewSprite(string FilePath, float PixelsPerUnit = 100.0f, SpriteMeshType spriteType = SpriteMeshType.Tight)
    {
        // Load a PNG or JPG image from disk to a Texture2D, assign this texture to a new sprite and return its reference
        Texture2D SpriteTexture = LoadTexture(FilePath);
        Sprite NewSprite = Sprite.Create(SpriteTexture, new Rect(0, 0, SpriteTexture.width, SpriteTexture.height), new Vector2(0, 0), PixelsPerUnit, 0 , spriteType);

        return NewSprite;
    }
1 Like

it does not work…

After upgrading Unity to 2018 I got the error:

Sprite does not contain a constructor

This is the updated code that works:

using UnityEngine;
using System.Collections;
using System.IO;


public class IMG2Sprite : MonoBehaviour
{


    // This script loads a PNG or JPEG image from disk and returns it as a Sprite
    // Drop it on any GameObject/Camera in your scene (singleton implementation)
    //
    // Usage from any other script:
    // MySprite = IMG2Sprite.instance.LoadNewSprite(FilePath, [PixelsPerUnit (optional)])

    private static IMG2Sprite _instance;

    public static IMG2Sprite instance
    {
        get
        {
            //If _instance hasn't been set yet, we grab it from the scene!
            //This will only happen the first time this reference is used.

            if (_instance == null)
                _instance = GameObject.FindObjectOfType<IMG2Sprite>();
            return _instance;
        }
    }

    public Sprite LoadNewSprite(string FilePath, float PixelsPerUnit = 100.0f, SpriteMeshType spriteType = SpriteMeshType.Tight)
    {       
        // Load a PNG or JPG image from disk to a Texture2D, assign this texture to a new sprite and return its reference
        Texture2D SpriteTexture = LoadTexture(FilePath);
        Sprite NewSprite = Sprite.Create(SpriteTexture, new Rect(0, 0, SpriteTexture.width, SpriteTexture.height), new Vector2(0, 0), PixelsPerUnit, 0, spriteType);

        return NewSprite;
    }

    public Texture2D LoadTexture(string FilePath)
    {

        // Load a PNG or JPG file from disk to a Texture2D
        // Returns null if load fails

        Texture2D Tex2D;
        byte[] FileData;

        if (File.Exists(FilePath))
        {
            FileData = File.ReadAllBytes(FilePath);
            Tex2D = new Texture2D(2, 2);           // Create new "empty" texture
            if (Tex2D.LoadImage(FileData))           // Load the imagedata into the texture (size is set automatically)
                return Tex2D;                 // If data = readable -> return texture
        }
        return null;                     // Return null if load failed
    }
}
5 Likes

I did some tests with your code and found out that if you set SpriteMeshType.FullRect instead of SpriteMeshType.Tight you increase the loading speed significantly.

In case some is wondering why Sprite.Create is so slow.

I tried tik_tikkers’ latest snippet and the sprites it generates are noticably blurrier than ones created with the Editor and loaded as Resources. I tried decreasing PixelsPerUnit as well as both types of SpriteMeshType to no effect. Has anyone encountered this?

(2019.4)

Try settings Tex2d.FilterMode = FilterMode.Point

Should remove the “blurriness” you’re talking about.

2 Likes

Thanks for the tip Thomas! The Unity code I am trying to emulate was using FilterMode.Bilinear; setting my code to use that, as well as setting Tex2D.wrapMode = TextureWrapMode.Clamp; got things to look a lot closer to the inspector result.

Thanks! I was looking for the solution for this for quite some time. You are the best!

In my case I am loading map tiles, 138 jpgs with size 500px x 475px. If I drop it to Unity Editor each image is 0.7MB but if I keep it as “.bytes” in the Resources, it’s only 50KBs each.

It all works since but the edge of the image was blurred. With your suggestion is now nice and clean.

This is how I load jpg from “.bytes” files, saving package size and loading it in the runtime. Seriously, Unity should have that option under sprite setup (slow load, no disk impact): :slight_smile:

 Texture2D texture = new Texture2D(500, 475, TextureFormat.RGB24, false);
texture.filterMode = FilterMode.Point;
TextAsset text = Resources.Load<TextAsset>($"MapTiles/Stage{stage}/{fileName}");
texture.LoadImage(text.bytes);

Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f), 100f, 1, SpriteMeshType.FullRect);

Before:
6796052--787832--upload_2021-2-3_0-7-38.png

After with FilterMode.Point:
6796052--787838--upload_2021-2-3_0-8-27.png

JPGs in the Resource:
6796052--787841--upload_2021-2-3_0-13-16.png

How do you get this running? I have copied the script but I don’t see any pngs converting to sprite? Could anyone help me understand how to use this script its very important.

using UnityEngine;
using System.Collections;
using System.IO;
public class IMG2Sprite : MonoBehaviour
{
    // This script loads a PNG or JPEG image from disk and returns it as a Sprite
    // Drop it on any GameObject/Camera in your scene (singleton implementation)
    //
    // Usage from any other script:
    // MySprite = IMG2Sprite.instance.LoadNewSprite(FilePath, [PixelsPerUnit (optional)])
    private static IMG2Sprite _instance;
    public static IMG2Sprite instance
    {
        get
        {
            //If _instance hasn't been set yet, we grab it from the scene!
            //This will only happen the first time this reference is used.
            if (_instance == null)
                _instance = GameObject.FindObjectOfType<IMG2Sprite>();
            return _instance;
        }
    }
    public Sprite LoadNewSprite(string FilePath, float PixelsPerUnit = 100.0f, SpriteMeshType spriteType = SpriteMeshType.Tight)
    {     
        // Load a PNG or JPG image from disk to a Texture2D, assign this texture to a new sprite and return its reference
        Texture2D SpriteTexture = LoadTexture(FilePath);
        Sprite NewSprite = Sprite.Create(SpriteTexture, new Rect(0, 0, SpriteTexture.width, SpriteTexture.height), new Vector2(0, 0), PixelsPerUnit, 0, spriteType);
        return NewSprite;
    }
    public Texture2D LoadTexture(string FilePath)
    {
        // Load a PNG or JPG file from disk to a Texture2D
        // Returns null if load fails
        Texture2D Tex2D;
        byte[] FileData;
        if (File.Exists(FilePath))
        {
            FileData = File.ReadAllBytes(FilePath);
            Tex2D = new Texture2D(2, 2);           // Create new "empty" texture
            if (Tex2D.LoadImage(FileData))           // Load the imagedata into the texture (size is set automatically)
                return Tex2D;                 // If data = readable -> return texture
        }
        return null;                     // Return null if load failed
    }
}

You can call the script and assign the return sprite to a sprite renderer:

public class LoadSprite : MonoBehaviour
{
    [SerializeField]
    private SpriteRenderer spriteRenderer;

    private void Start()
    {
        spriteRenderer.sprite = IMG2Sprite.instance.LoadNewSprite("C:\image.png");
    }
}

I didn’t test the script but you should understand how to use it.

And by convention I prefer to use PascalCase for public field and camelCase for private field, local field and method parameter. Here’s the updated version of the script:

using UnityEngine;
using System.Collections;
using System.IO;
public class IMG2Sprite : MonoBehaviour
{
    // This script loads a PNG or JPEG image from disk and returns it as a Sprite
    // Drop it on any GameObject/Camera in your scene (singleton implementation)
    //
    // Usage from any other script:
    // MySprite = IMG2Sprite.instance.LoadNewSprite(FilePath, [PixelsPerUnit (optional)])
    private static IMG2Sprite _instance;
    public static IMG2Sprite Instance
    {
        get
        {
            //If _instance hasn't been set yet, we grab it from the scene!
            //This will only happen the first time this reference is used.
            if (_instance == null)
                _instance = GameObject.FindObjectOfType<IMG2Sprite>();
            return _instance;
        }
    }
    public Sprite LoadNewSprite(string filePath, float pixelsPerUnit = 100.0f, SpriteMeshType spriteType = SpriteMeshType.Tight)
    {
        // Load a PNG or JPG image from disk to a Texture2D, assign this texture to a new sprite and return its reference
        Texture2D spriteTexture = LoadTexture(filePath);
        Sprite newSprite = Sprite.Create(spriteTexture, new Rect(0, 0, spriteTexture.width, spriteTexture.height), new Vector2(0, 0), pixelsPerUnit, 0, spriteType);
        return newSprite;
    }
    public Texture2D LoadTexture(string filePath)
    {
        // Load a PNG or JPG file from disk to a Texture2D
        // Returns null if load fails
        Texture2D tex2D;
        byte[] fileData;
        if (File.Exists(filePath))
        {
            fileData = File.ReadAllBytes(filePath);
            tex2D = new Texture2D(2, 2);           // Create new "empty" texture
            if (tex2D.LoadImage(fileData))           // Load the imagedata into the texture (size is set automatically)
                return tex2D;                 // If data = readable -> return texture
        }
        return null;                     // Return null if load failed
    }
}