TGA Loader for Unity3D

Hey guys! This is my TGA Loader, It supports uncompressed 32-bit or 24-bit True Color images

Here is the code :)

using System;
using System.Collections;
using UnityEngine;
using System.IO;
using System.Collections.Generic;
public Texture2D LoadTGA(string TGAFile){
    using (BinaryReader r = new BinaryReader(File.Open(TGAFile, FileMode.Open))) {
            byte IDLength = r.ReadByte();
byte ColorMapType = r.ReadByte();
byte ImageType = r.ReadByte();
Int16 CMapStart = r.ReadInt16();
Int16 CMapLength = r.ReadInt16();
byte CMapDepth = r.ReadByte();
Int16 XOffset = r.ReadInt16();
Int16 YOffset = r.ReadInt16();
Int16 Width = r.ReadInt16();
Int16 Height = r.ReadInt16();
byte PixelDepth = r.ReadByte();
byte ImageDescriptor = r.ReadByte();
if (ImageType == 0) {
Debug.Log("Unsupported TGA file! No image data");
} else if (ImageType == 3 | ImageType == 11) {
Debug.Log("Unsupported TGA file! Not truecolor");
} else if (ImageType == 9 | ImageType == 10) {
Debug.Log("Unsupported TGA file! Colormapped");

}
//     MsgBox("Dimensions are "  Width  ","  Height)
Texture2D b = new Texture2D(Width, Height);
for (int y = 0; y <= b.height - 1; y++) {
    for (int x = 0; x <= b.width - 1; x++) {

        if (PixelDepth == 32) {

         float red = Convert.ToSingle(r.ReadByte());
         float green = Convert.ToSingle(r.ReadByte());
         float blue = Convert.ToSingle(r.ReadByte());
         float alpha = Convert.ToSingle(r.ReadByte());
         alpha /=255;
         green /= 255;
         blue /= 255;
         red /= 255;
        Color cl = new Color(blue ,green ,red ,alpha );
            b.SetPixel(x, y, cl);


        } else {

         float red = Convert.ToSingle(r.ReadByte());
         float green = Convert.ToSingle(r.ReadByte());
         float blue = Convert.ToSingle(r.ReadByte());


         green /= 255;
         blue /= 255;
         red /= 255;
        Color cl = new Color(blue ,green ,red ,1 );
            b.SetPixel(x, y, cl);


        }

    }
}
            b.Apply();

return b;
        }
    }

Remember to give credit if you use it :)

Cool. Code looks okay on first glance, though formatting appears to be an issue...

Yeah, I made this quickly for a project I am doing.

I would advise using Color32 instead of Color. Also SetPixels32 instead of SetPixel.

--Eric

What's the difference?

Using SetPixels is faster than calling SetPixel repeatedly, and Color32 is faster than Color (since there's no need to convert ints to normalized floats) and uses 4X less RAM.

--Eric

Ah alright, I'll use this in my BMP loader which I am working on

Thanks for sharing aaro4130! :)

I slimmed this down and optimized it a bit. I use it for loading TGA Quake 3 textures, but you made it, so I feel I should share my alterations.

I use an array to store Color32 objects (instead of Color) and work with plain bytes, which avoids all the bitwise and Convert. stuff. I also use SetPixels32 instead of hitting each pixel one by one. It's a bit faster. It is also a complete static class that can be passed either a file name as a string, or a raw stream. I'm using it with SharpZipLib to pull Quake 3 textures directly from .pk3 files, and it works perfectly. The previous design also checked the bitdepth in between every pixel, which is kind of crazy.

// This was made by aaro4130 on the Unity forums.  Thanks boss!
// It's been optimized and slimmed down for the purpose of loading Quake 3 TGA textures from memory streams.

using System;
using System.IO;
using UnityEngine;

public static class TGALoader
{

    public static Texture2D LoadTGA(string fileName)
    {
        using (var imageFile = File.OpenRead(fileName))
        {
            return LoadTGA(imageFile);
        }
    }

    public static Texture2D LoadTGA(Stream TGAStream)
    {

        using (BinaryReader r = new BinaryReader(TGAStream))
        {
            // Skip some header info we don't care about.
            // Even if we did care, we have to move the stream seek point to the beginning,
            // as the previous method in the workflow left it at the end.
            r.BaseStream.Seek(12, SeekOrigin.Begin);

            short width = r.ReadInt16();
            short height = r.ReadInt16();
            int bitDepth = r.ReadByte();

            // Skip a byte of header information we don't care about.
            r.BaseStream.Seek(1, SeekOrigin.Current);

            Texture2D tex = new Texture2D(width, height);
            Color32[] pulledColors = new Color32[width * height];

            if (bitDepth == 32)
            {
                for (int i = 0; i < width * height; i++)
                {
                    byte red = r.ReadByte();
                    byte green = r.ReadByte();
                    byte blue = r.ReadByte();
                    byte alpha = r.ReadByte();

                    pulledColors [i] = new Color32(blue, green, red, alpha);
                }
            } else if (bitDepth == 24)
            {
                for (int i = 0; i < width * height; i++)
                {
                    byte red = r.ReadByte();
                    byte green = r.ReadByte();
                    byte blue = r.ReadByte();

                    pulledColors [i] = new Color32(blue, green, red, 1);
                }
            } else
            {
                throw new Exception("TGA texture had non 32/24 bit depth.");
            }

            tex.SetPixels32(pulledColors);
            tex.Apply();
            return tex;

        }
    }
}
1 Like

Wow. Thanks !

Glad people are using it.

at System.IO.FileStream..ctor (System.String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, Boolean anonymous, FileOptions options) [0x00000] in :0
at System.IO.FileStream..ctor (System.String path, FileMode mode, FileAccess access, FileShare share) [0x00000] in :0
at System.IO.File.OpenRead (System.String path) [0x00000] in :0
at TGALoader.LoadTGA (System.String fileName) [0x00000] in :0
at XLua.CSObjectWrap.TGALoaderWrap.m_LoadTGA_xlua_st (IntPtr L) [0x00000] in :0
stack traceback:
: in field 'LoadTGA'

Fast loader for 32-bit uncompressed TGA files:

using System;
using System.IO;
using UnityEngine;
public static class TGALoader
{
    // Loads 32-bit (RGBA) uncompressed TGA. Actually, due to TARGA file structure, BGRA32 is good option...
    // Disabled mipmaps. Disabled read/write option, to release texture memory copy.
    public static Texture2D LoadTGA(string fileName)
    {
        try
        {
            BinaryReader reader = new BinaryReader(File.OpenRead(fileName));
            reader.BaseStream.Seek(12, SeekOrigin.Begin);    
            short width = reader.ReadInt16();
            short height = reader.ReadInt16();
            reader.BaseStream.Seek(2, SeekOrigin.Current);
            byte[] source = reader.ReadBytes(width * height * 4);
            reader.Close();
            Texture2D texture = new Texture2D(width, height, TextureFormat.BGRA32, false);
            texture.LoadRawTextureData(source);
            texture.name = Path.GetFileName(fileName);
            texture.Apply(false, true);
            return texture;
        }
        catch (Exception)
        {
            return Texture2D.blackTexture;
        }
    }
}
1 Like

@Przemyslaw_Zaworski thanks very much for this! Worked perfectly.

[quote=“Przemyslaw_Zaworski”, post:12, topic: 498708]
Fast loader for 32-bit uncompressed TGA files:

using System;
using System.IO;
using UnityEngine;
public static class TGALoader
{
    // Loads 32-bit (RGBA) uncompressed TGA. Actually, due to TARGA file structure, BGRA32 is good option...
    // Disabled mipmaps. Disabled read/write option, to release texture memory copy.
    public static Texture2D LoadTGA(string fileName)
    {
        try
        {
            BinaryReader reader = new BinaryReader(File.OpenRead(fileName));
            reader.BaseStream.Seek(12, SeekOrigin.Begin);  
            short width = reader.ReadInt16();
            short height = reader.ReadInt16();
            reader.BaseStream.Seek(2, SeekOrigin.Current);
            byte[] source = reader.ReadBytes(width * height * 4);
            reader.Close();
            Texture2D texture = new Texture2D(width, height, TextureFormat.BGRA32, false);
            texture.LoadRawTextureData(source);
            texture.name = Path.GetFileName(fileName);
            texture.Apply(false, true);
            return texture;
        }
        catch (Exception)
        {
            return Texture2D.blackTexture;
        }
    }
}

[/quote]
Hi! The code doesn’t work for all tga samples I’ve found here: https://people.math.sc.edu/Burkardt/data/tga/tga.html

“TGA texture had non 32/24 bit depth.” or “Unable to read beyond the end of the stream.”

Yes, because I wrote "Loads 32-bit (RGBA) uncompressed TGA." So it doesn't work for TGA with lossless RLE compression. Anyway, for game development I recommend to use DDS file format, because it is the best in quality and performance ratio, and it is easy to use compression and mipmaps. Examples:

Standard DDS loader:

using UnityEngine;
using System.IO;
using System;

public static class LoadDDS
{
    public static Texture2D Load(string path)
    {
        try
        {
            BinaryReader reader = new BinaryReader(File.OpenRead(path));
            long length = new FileInfo(path).Length;
            byte[] header = reader.ReadBytes(128);
            int height = header[13] * 256 + header[12];
            int width = header[17] * 256 + header[16];
            bool mipmaps = header[28] > 0;
            TextureFormat textureFormat = header[87] == 49 ? TextureFormat.DXT1 : TextureFormat.DXT5;
            byte[] source = reader.ReadBytes(Convert.ToInt32(length) - 128);
            reader.Close();
            Texture2D texture = new Texture2D(width, height, textureFormat, mipmaps);
            texture.LoadRawTextureData(source);
            texture.name = Path.GetFileName(path);
            texture.Apply(false, true);
            return texture;
        }
        catch (Exception)
        {
            return Texture2D.blackTexture;
        }
    }
}

Async DDS loader:

using UnityEngine;
using System;
using System.IO;

public class LoadTextureAsyncFromDDS : MonoBehaviour
{
    async void Load (Material material, string property, string filepath)
    {
        FileStream stream = File.Open(filepath, FileMode.Open);
        long length = stream.Length;
        byte[] header = new byte[128];
        await stream.ReadAsync(header, 0, 128);
        int height = header[13] * 256 + header[12];
        int width = header[17] * 256 + header[16];
        bool mipmaps = header[28] > 0;
        TextureFormat textureFormat = header[87] == 49 ? TextureFormat.DXT1 : TextureFormat.DXT5;
        byte[] source = new byte[Convert.ToInt32(length) - 128];
        await stream.ReadAsync(source, 0, Convert.ToInt32(length) - 128);
        stream.Close();
        Texture2D texture = new Texture2D(width, height, textureFormat, mipmaps);
        texture.LoadRawTextureData(source);
        texture.name = Path.GetFileName(filepath);
        texture.Apply(false, true);
        material.SetTexture(property, texture);
    }

    void Delete (Material material, string property)
    {
        Destroy(material.GetTexture(property));
        material.SetTexture(property, null);
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.L))
            Load(GetComponent<Renderer>().material, "_MainTex", System.IO.Path.Combine(Application.streamingAssetsPath, "plasma.dds"));
        if (Input.GetKeyDown(KeyCode.Space))
            Delete(GetComponent<Renderer>().material, "_MainTex");
    }
}

Or use PNG , this is async PNG loader:

using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
using System;
using System.Runtime.CompilerServices;

public class UnityWebRequestAwaiter : INotifyCompletion
{
    UnityWebRequestAsyncOperation request;
    Action action;

    public UnityWebRequestAwaiter(UnityWebRequestAsyncOperation request)
    {
        this.request = request;
        request.completed += OnRequestCompleted;
    }

    public bool IsCompleted { get { return request.isDone; } }

    public void GetResult() { }

    public void OnCompleted(Action action)
    {
        this.action = action;
    }

    private void OnRequestCompleted(AsyncOperation command)
    {
        action();
    }
}

public static class ExtensionMethods
{
    public static UnityWebRequestAwaiter GetAwaiter(this UnityWebRequestAsyncOperation request)
    {
        return new UnityWebRequestAwaiter(request);
    }
}

public class LoadTextureAsync : MonoBehaviour
{
    async void Load (Material material, string property, string filepath)
    {
        UnityWebRequest request = UnityWebRequestTexture.GetTexture(filepath);
        await request.SendWebRequest();
        material.SetTexture(property, ((DownloadHandlerTexture)request.downloadHandler).texture);
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
            Load(GetComponent<Renderer>().material, "_MainTex", System.IO.Path.Combine(Application.streamingAssetsPath, "plasma.png"));
    }
}
1 Like

[quote=“Przemyslaw_Zaworski”, post:15, topic: 498708]
Yes, because I wrote “Loads 32-bit (RGBA) uncompressed TGA.” So it doesn’t work for TGA with lossless RLE compression. Anyway, for game development I recommend to use DDS file format, because it is the best in quality and performance ratio, and it is easy to use compression and mipmaps. Examples:

Standard DDS loader:

using UnityEngine;
using System.IO;
using System;

public static class LoadDDS
{
    public static Texture2D Load(string path)
    {
        try
        {
            BinaryReader reader = new BinaryReader(File.OpenRead(path));
            long length = new FileInfo(path).Length;
            byte[] header = reader.ReadBytes(128);
            int height = header[13] * 256 + header[12];
            int width = header[17] * 256 + header[16];
            bool mipmaps = header[28] > 0;
            TextureFormat textureFormat = header[87] == 49 ? TextureFormat.DXT1 : TextureFormat.DXT5;
            byte[] source = reader.ReadBytes(Convert.ToInt32(length) - 128);
            reader.Close();
            Texture2D texture = new Texture2D(width, height, textureFormat, mipmaps);
            texture.LoadRawTextureData(source);
            texture.name = Path.GetFileName(path);
            texture.Apply(false, true);
            return texture;
        }
        catch (Exception)
        {
            return Texture2D.blackTexture;
        }
    }
}

Async DDS loader:

using UnityEngine;
using System;
using System.IO;

public class LoadTextureAsyncFromDDS : MonoBehaviour
{
    async void Load (Material material, string property, string filepath)
    {
        FileStream stream = File.Open(filepath, FileMode.Open);
        long length = stream.Length;
        byte[] header = new byte[128];
        await stream.ReadAsync(header, 0, 128);
        int height = header[13] * 256 + header[12];
        int width = header[17] * 256 + header[16];
        bool mipmaps = header[28] > 0;
        TextureFormat textureFormat = header[87] == 49 ? TextureFormat.DXT1 : TextureFormat.DXT5;
        byte[] source = new byte[Convert.ToInt32(length) - 128];
        await stream.ReadAsync(source, 0, Convert.ToInt32(length) - 128);
        stream.Close();
        Texture2D texture = new Texture2D(width, height, textureFormat, mipmaps);
        texture.LoadRawTextureData(source);
        texture.name = Path.GetFileName(filepath);
        texture.Apply(false, true);
        material.SetTexture(property, texture);
    }

    void Delete (Material material, string property)
    {
        Destroy(material.GetTexture(property));
        material.SetTexture(property, null);
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.L))
            Load(GetComponent<Renderer>().material, "_MainTex", System.IO.Path.Combine(Application.streamingAssetsPath, "plasma.dds"));
        if (Input.GetKeyDown(KeyCode.Space))
            Delete(GetComponent<Renderer>().material, "_MainTex");
    }
}

Or use PNG , this is async PNG loader:

using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
using System;
using System.Runtime.CompilerServices;

public class UnityWebRequestAwaiter : INotifyCompletion
{
    UnityWebRequestAsyncOperation request;
    Action action;

    public UnityWebRequestAwaiter(UnityWebRequestAsyncOperation request)
    {
        this.request = request;
        request.completed += OnRequestCompleted;
    }

    public bool IsCompleted { get { return request.isDone; } }

    public void GetResult() { }

    public void OnCompleted(Action action)
    {
        this.action = action;
    }

    private void OnRequestCompleted(AsyncOperation command)
    {
        action();
    }
}

public static class ExtensionMethods
{
    public static UnityWebRequestAwaiter GetAwaiter(this UnityWebRequestAsyncOperation request)
    {
        return new UnityWebRequestAwaiter(request);
    }
}

public class LoadTextureAsync : MonoBehaviour
{
    async void Load (Material material, string property, string filepath)
    {
        UnityWebRequest request = UnityWebRequestTexture.GetTexture(filepath);
        await request.SendWebRequest();
        material.SetTexture(property, ((DownloadHandlerTexture)request.downloadHandler).texture);
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
            Load(GetComponent<Renderer>().material, "_MainTex", System.IO.Path.Combine(Application.streamingAssetsPath, "plasma.png"));
    }
}

[/quote]
Thanks for replying! I was asking in context of an image editor. Anyway, I’ve found another solution to load any types of TGA.

[quote=“hippogames”, post:16, topic: 498708]
Thanks for replying! I was asking in context of an image editor. Anyway, I’ve found another solution to load any types of TGA.
[/quote]
I know it’s been a while since you posted this, but would you mind sharing your TGA loading solution?
Thanks!