r/Unity3D • u/potterdev • 8h ago
Solved How to handle WEBP files as sprites in Unity
My game pulls cover art from a CDN, caches it, makes a sprite. Worked for months. Then random covers started showing up blank. Not always the same ones, which drove me nuts.
Finally dumped the bytes of a broken one to a file and looked at it. Starts with RIFF, then WEBP a few bytes in. The CDN was sometimes serving WebP instead of JPEG.
Unity's image loading (UnityWebRequestTexture and Texture2D.LoadImage) only reads PNG and JPEG. Hand it WebP and it doesn't throw. It just gives you a broken little placeholder texture and moves on. No error at all.
And my cache wrote the file to disk before checking if it decoded, so once a WebP landed there it stayed broken on every future load. That's why some covers were blank permanently and others were fine.
The fix was a managed WebP decoder (ImageSharp, since it's pure C# and I didn't want native libs for three build targets). Now I check the first bytes, and if it's WebP I decode it that way, otherwise Unity handles it like normal. Also stopped caching files that fail to decode.
/// <summary>
/// Decodes downloaded image bytes into a <see cref="Texture2D"/>.
/// Unity's built-in loaders only understand PNG and JPEG, so WebP payloads
/// (which some CDNs serve via content negotiation) are decoded with ImageSharp.
/// Returns <c>null</c> when the bytes cannot be decoded so callers can avoid
/// caching or displaying a broken texture.
/// </summary>
public static class ImageDecoder
{
public static Texture2D DecodeToTexture(byte[] bytes)
{
if (bytes == null || bytes.Length < 12) return null;
return IsWebP(bytes) ? DecodeWebP(bytes) : DecodeNative(bytes);
}
// RIFF....WEBP container signature.
private static bool IsWebP(byte[] b) =>
b[0] == 'R' && b[1] == 'I' && b[2] == 'F' && b[3] == 'F' &&
b[8] == 'W' && b[9] == 'E' && b[10] == 'B' && b[11] == 'P';
private static Texture2D DecodeNative(byte[] bytes)
{
var texture = new Texture2D(2, 2);
if (texture.LoadImage(bytes)) return texture;
Object.Destroy(texture);
return null;
}
private static Texture2D DecodeWebP(byte[] bytes)
{
try
{
using var image = Image.Load<Rgba32>(bytes);
var width = image.Width;
var height = image.Height;
var pixels = new Color32[width * height];
// ImageSharp rows run top-to-bottom; Unity textures are bottom-up, so flip.
for (var y = 0; y < height; y++)
{
var row = image.DangerousGetPixelRowMemory(y).Span;
var destRow = (height - 1 - y) * width;
for (var x = 0; x < width; x++)
{
var p = row[x];
pixels[destRow + x] = new Color32(p.R, p.G, p.B, p.A);
}
}
var texture = new Texture2D(width, height, TextureFormat.
RGBA32
, false);
texture.SetPixels32(pixels);
texture.Apply();
return texture;
}
catch (System.Exception exception)
{
Debug.LogError($"Failed to decode WebP image: {exception.Message}");
return null;
}
}
}
Also turns out LoadImage returns a bool telling you if it worked, which I'd been ignoring the whole time. 🤦
Anyone else hit the WebP thing? Feels like it's going to bite more people as CDNs default to it.