using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using Barotrauma.IO; using System.Threading.Tasks; using Lidgren.Network; using Color = Microsoft.Xna.Framework.Color; namespace Barotrauma { /// /// Based on http://jakepoz.com/jake_poznanski__background_load_xna.html /// public static class TextureLoader { public static Texture2D PlaceHolderTexture { get; private set; } private static volatile bool cancelAll = false; public static void Init(GraphicsDevice graphicsDevice, bool needsBmp = false) { _graphicsDevice = graphicsDevice; Color[] data = new Color[32 * 32]; for (int i = 0; i < 32 * 32; i++) { data[i] = Color.Magenta; } CrossThread.RequestExecutionOnMainThread(() => { PlaceHolderTexture = new Texture2D(graphicsDevice, 32, 32); PlaceHolderTexture.SetData(data); }); } public static void CancelAll() { cancelAll = true; } private static byte[] CompressDxt5(byte[] data, int width, int height) { using (System.IO.MemoryStream mstream = new System.IO.MemoryStream()) { for (int y = 0; y < height; y += 4) { for (int x = 0; x < width; x += 4) { int offset = x * 4 + y * 4 * width; CompressDxt5Block(data, offset, width, mstream); } } return mstream.ToArray(); } } private static void CompressDxt5Block(byte[] data, int offset, int width, System.IO.Stream output) { int r1 = 255, g1 = 255, b1 = 255, a1 = 255; int r2 = 0, g2 = 0, b2 = 0, a2 = 0; //determine the two colors to interpolate between: //color 1 represents lowest luma, color 2 represents highest luma for (int i = 0; i < 16; i++) { int pixelOffset = offset + (4 * ((i % 4) + (width * (i >> 2)))); int r, g, b, a; r = data[pixelOffset + 0]; g = data[pixelOffset + 1]; b = data[pixelOffset + 2]; a = data[pixelOffset + 3]; if (r * 299 + g * 587 + b * 114 < r1 * 299 + g1 * 587 + b1 * 114) { r1 = r; g1 = g; b1 = b; } if (r * 299 + g * 587 + b * 114 > r2 * 299 + g2 * 587 + b2 * 114) { r2 = r; g2 = g; b2 = b; } if (a < a1) { a1 = a; } if (a > a2) { a2 = a; } } //convert the colors to rgb565 (16-bit rgb) int r1_565 = (r1 * 0x1f) / 0xff; if (r1_565 > 0x1f) { r1_565 = 0x1f; } int g1_565 = (g1 * 0x3f) / 0xff; if (g1_565 > 0x3f) { g1_565 = 0x3f; } int b1_565 = (b1 * 0x1f) / 0xff; if (b1_565 > 0x1f) { b1_565 = 0x1f; } int r2_565 = (r2 * 0x1f) / 0xff; if (r2_565 > 0x1f) { r2_565 = 0x1f; } int g2_565 = (g2 * 0x3f) / 0xff; if (g2_565 > 0x3f) { g2_565 = 0x3f; } int b2_565 = (b2 * 0x1f) / 0xff; if (b2_565 > 0x1f) { b2_565 = 0x1f; } //luma is also used to determine which color on the palette //most closely resembles each pixel to compress, so we //calculate this here int y1 = r1 * 299 + g1 * 587 + b1 * 114; int y2 = r2 * 299 + g2 * 587 + b2 * 114; byte[] newData = new byte[16]; for (int i = 0; i < 16; i++) { int pixelOffset = offset + (4 * ((i % 4) + (width * (i >> 2)))); int r, g, b, a; r = data[pixelOffset + 0]; g = data[pixelOffset + 1]; b = data[pixelOffset + 2]; a = data[pixelOffset + 3]; if (a1 < a2) { a -= a1; a = (a * 0x7) / (a2 - a1); if (a > 0x7) { a = 0x7; } switch (a) { case 0: a = 1; break; case 1: a = 7; break; case 2: a = 6; break; case 3: a = 5; break; case 4: a = 4; break; case 5: a = 3; break; case 6: a = 2; break; case 7: a = 0; break; } } else { a = 0; } NetBitWriter.WriteUInt32((uint)a, 3, newData, 16 + (i * 3)); int y = r * 299 + g * 587 + b * 114; int max = y2 - y1; int diffY = y - y1; int paletteIndex; if (diffY < max / 4) { paletteIndex = 0; } else if (diffY < max / 2) { paletteIndex = 2; } else if (diffY < max * 3 / 4) { paletteIndex = 3; } else { paletteIndex = 1; } newData[12 + (i / 4)] |= (byte)(paletteIndex << (2 * (i % 4))); } newData[0] = (byte)a2; newData[1] = (byte)a1; newData[9] = (byte)((r1_565 << 3) | (g1_565 >> 3)); newData[8] = (byte)((g1_565 << 5) | b1_565); newData[11] = (byte)((r2_565 << 3) | (g2_565 >> 3)); newData[10] = (byte)((g2_565 << 5) | b2_565); output.Write(newData, 0, 16); } public static Texture2D FromFile(string path, bool compress = true, bool mipmap = false) { using (FileStream fileStream = File.OpenRead(path)) { return FromStream(fileStream, path, compress, mipmap); } } public static Texture2D FromStream(System.IO.Stream stream, string path = null, bool compress = true, bool mipmap = false) { try { path = path.CleanUpPath(); byte[] textureData = null; textureData = Texture2D.TextureDataFromStream(stream, out int width, out int height, out int channels); SurfaceFormat format = SurfaceFormat.Color; if (GameSettings.CurrentConfig.Graphics.CompressTextures && compress) { if (((width & 0x03) == 0) && ((height & 0x03) == 0)) { textureData = CompressDxt5(textureData, width, height); format = SurfaceFormat.Dxt5; mipmap = false; } else { DebugConsole.AddWarning($"Could not compress a texture because the dimensions aren't a multiple of 4 (path: {path ?? "null"}, size: {width}x{height})"); } } Texture2D tex = null; CrossThread.RequestExecutionOnMainThread(() => { if (cancelAll) { return; } tex = new Texture2D(_graphicsDevice, width, height, mipmap, format); tex.SetData(textureData); }); return tex; } catch (Exception e) { #if WINDOWS if (e is SharpDX.SharpDXException) { throw; } #endif DebugConsole.ThrowError(string.IsNullOrEmpty(path) ? "Loading texture from stream failed!" : "Loading texture \"" + path + "\" failed!", e); return null; } } private static GraphicsDevice _graphicsDevice; } }