Texture2D.LoadImage() distorting colors

I need to load a PNG into a Texture2D and then read precisely the RGB values from each pixel.
However Unity is distorting it big time. I am sure the original PNG does not have a color profile embedded. And it is read perfectly, with the correct RGB values, by all the image viewers I tried

If I do

Texture2D tex  = new Texture2D(128, 128, TextureFormat.RGB24, true);
byte[] fileData;
string filePath = @"c:\temp\testColor.png"
if (File.Exists(filePath)) 
{     
       fileData = File.ReadAllBytes(filePath);       
       tex.LoadImage(fileData)
}

Color32[] pixels = tex.GetPixels32();
Color32 testColor32 = pixels[16];
Debug.Log(testColor32);

The RGB values printed are not the values present on the image. I created it myself using PngBitmapEncoder on Visual Studio (separate program) and am sure of the RGB values, which are well read by any image viewer (the test images have uniform color, so I can pick any pixel for test).

This may have to do with Texture2D.LoadImage() compressing the image to DXT5. Is there a way to disable compression on load? Test image attached, its uniform RGB values are 0, 154, 10. Unity reads 0, 146, 7
2854138--208610--testColor.png

1 Like

LoadImage uses ARGB32 for PNG files by default. It will only do compression if you use DXT1 or DXT5 as the format. I’d start by omitting as much explicit info as possible and let Unity handle it.

Texture2D tex = new Texture2D(1, 1);
// LoadImage will default the format and resize the dimensions
text.LoadImage(fileData);

I started by being very “omissive” and frankly there was no difference in the results whatsoever. I guess LoadImage() supersedes everything.
I am transmitting many images per second through the network, so need to have compressed PNGs. I wish I could just let Unity handle it as you suggest, but it seems to mess up colors on load.

So wait - the originals are compressed? It really just seems like you’ve got your formats mixed up.

PNG supports lossless compression. Anyway it shouldn’t matter. RGB values should be preserved. Unity can read (and display) the file but the values are a bit off.

Your Texture2D constructor is telling it to use mipmaps — I’d change that to false. Also, add one more parameter, which tells it to use the linear (true) or sRGB (false) color space. I suspect the color space is responsible for the slight change in color you’re seeing.

I tried all the combinations already. But you are right, I should not have posted this code with mipmaps on. Also tried right now to change the color space from sRGB to linear and back. No change. My guess is LoadImage() supersedes all these parameters and forces a color space.

1 Like

You could verify this by logging the format of the image after loading it

4 years later and this still seems to be an issue…

So, to give some further findings.
Loading a texture with loadimage results in graphics format with a value “88”. Its unnamed (its not a part of the enum!).
It happens regardless of textures original graphics format.
Texture format, on the other hand is overwritten to ARGB32

I presume this isn’t intentional, since the texture seems to always end up compressed which renders LoadImage quite useless for a wide category of uses. The fact that “88” isn’t even in the enum seems to point to a bug or an oversight too.

Well, if the image contains gamma information, Unity will use it to gamma correct the image. Since almost all png images usually have a gAMA chunk, pretty much all pngs would be affected in one way or the other. Though it depends on what gamma information has been stored. The PNG specification states:

I guess it’s not really a Unity issue since if gama correction information is present in an image, it should be used. When importing an image in Unity directly you actually have more options to control the import process compared to LoadImage.

I’ve written a PNG chunk parser some time ago which can be used to remove, insert or modify chunks in a png file. Note that in the case of the linked question the problematic bit was the gamma information. However it’s possible that other metadata could also cause issues. You could preprocess your image and remove unwanted chunks from the actual png file, or you could use it at runtime to strip out the unwanted chunks on the fly before passing the data to LoadImage

1 Like

I would argue that it is a Unity issue.
If virtually all popular PNG file loaders (including ones from System.Drawing) as well as popular image editing programs (paint/paint.net/photoshop/gimp/aseprite) load the exact same RGB values and Unity doesn’t, it should be either changed or documented in detail.

Importing an image in Unity is all fine and dandy, except when you don’t have the image at build time. Modifying it using external tools is also somewhat of a solution… Except when it’s your users that are providing the image.

I would like to see this behavior either modified or clarified as intended in the documentation.
Especially the elusive “GraphicsFormat 88”. I will most likely raise an issue about this topic once I find a day to make a project to present it.

I just tested, it log RGBA(0, 154, 10, 255)
Win10, Unity 2021.1.13f1c1, URP

Still seems to be happening.
I made a test image in Krita.

When saving out as a png with ‘Forced Convert to sRGB’ ticked;
After LoadImage the image comes across pixel perfect.

When saving out with the ‘Forced Convert to sRGB’ UN-ticked;
The colours are somewhat off.
0,0,0 → 0,0,0
255,0,0 → 255,0,0
0,255,0 → 0,255,0
0,0,255 → 0,0,255
128,0,0 → 116,0,0
0,128,0 → 0,128,0
0,0,128 → 0,0,122

Red, Green and Blue are being treated differently.
It looks like some sort of Gamma thing but the effect is far more subtle than sRGB / RGB.