using System; using System.Collections.Generic; using System.IO; using FSO.Files.Utils; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; using FSO.Common.Utils; using FSO.Common; using FSO.Common.Rendering; namespace FSO.Files.Formats.IFF.Chunks { /// /// This chunk type holds a number of paletted sprites that share a common color palette and lack z-buffers and /// alpha buffers. SPR# chunks can be either big-endian or little-endian, which must be determined by comparing /// the first two bytes to zero (since no version number uses more than two bytes). /// public class SPR : IffChunk { public List Frames { get; internal set; } public ushort PaletteID; private List Offsets; public ByteOrder ByteOrd; public bool WallStyle; /// /// Reads a SPR chunk from a stream. /// /// An Iff instance. /// A Stream object holding a SPR chunk. public override void Read(IffFile iff, Stream stream) { using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN)) { var version1 = io.ReadUInt16(); var version2 = io.ReadUInt16(); uint version = 0; if (version1 == 0) { io.ByteOrder = ByteOrder.BIG_ENDIAN; version = (uint)(((version2|0xFF00)>>8) | ((version2&0xFF)<<8)); } else { version = version1; } ByteOrd = io.ByteOrder; var spriteCount = io.ReadUInt32(); PaletteID = (ushort)io.ReadUInt32(); Frames = new List(); if (version != 1001) { var offsetTable = new List(); for (var i = 0; i < spriteCount; i++) { offsetTable.Add(io.ReadUInt32()); } Offsets = offsetTable; for (var i = 0; i < spriteCount; i++) { var frame = new SPRFrame(this); io.Seek(SeekOrigin.Begin, offsetTable[i]); var guessedSize = ((i + 1 < offsetTable.Count) ? offsetTable[i + 1] : (uint)stream.Length) - offsetTable[i]; frame.Read(version, io, guessedSize); Frames.Add(frame); } } else { while (io.HasMore) { var frame = new SPRFrame(this); frame.Read(version, io, 0); Frames.Add(frame); } } } } } /// /// The frame (I.E sprite) of a SPR chunk. /// public class SPRFrame : ITextureProvider { public static PALT DEFAULT_PALT = new PALT(Color.Black); public uint Version; private SPR Parent; private Texture2D PixelCache; private byte[] ToDecode; /// /// Constructs a new SPRFrame instance. /// /// A SPR parent. public SPRFrame(SPR parent) { this.Parent = parent; } /// /// Reads a SPRFrame from a stream. /// /// An Iff instance. /// A Stream object holding a SPRFrame. public void Read(uint version, IoBuffer io, uint guessedSize) { if (version == 1001) { var spriteFersion = io.ReadUInt32(); var size = io.ReadUInt32(); this.Version = spriteFersion; if (IffFile.RETAIN_CHUNK_DATA) ReadDeferred(1001, io); else ToDecode = io.ReadBytes(size); } else { this.Version = version; if (IffFile.RETAIN_CHUNK_DATA) ReadDeferred(1000, io); else ToDecode = io.ReadBytes(guessedSize); } } public void ReadDeferred(uint version, IoBuffer io) { var reserved = io.ReadUInt32(); var height = io.ReadUInt16(); var width = io.ReadUInt16(); this.Init(width, height); this.Decode(io); } public void DecodeIfRequired() { if (ToDecode != null) { using (IoBuffer buf = IoBuffer.FromStream(new MemoryStream(ToDecode), Parent.ByteOrd)) { ReadDeferred(Version, buf); } ToDecode = null; } } /// /// Decodes this SPRFrame. /// /// IOBuffer used to read a SPRFrame. private void Decode(IoBuffer io) { var palette = Parent.ChunkParent.Get(Parent.PaletteID); if (palette == null) { palette = DEFAULT_PALT; } var y = 0; var endmarker = false; while (!endmarker){ var command = io.ReadByte(); var count = io.ReadByte(); switch (command){ /** Start marker **/ case 0x00: case 0x10: break; /** Fill row with pixel data **/ case 0x04: var bytes = count - 2; var x = 0; while (bytes > 0){ var pxCommand = io.ReadByte(); var pxCount = io.ReadByte(); bytes -= 2; switch (pxCommand){ /** Next {n} pixels are transparent **/ case 0x01: x += pxCount; break; /** Next {n} pixels are the same palette color **/ case 0x02: var index = io.ReadByte(); var padding = io.ReadByte(); bytes -= 2; var color = palette.Colors[index]; for (var j=0; j < pxCount; j++){ this.SetPixel(x, y, color); x++; } break; /** Next {n} pixels are specific palette colours **/ case 0x03: for (var j=0; j < pxCount; j++){ var index2 = io.ReadByte(); var color2 = palette.Colors[index2]; this.SetPixel(x, y, color2); x++; } bytes -= pxCount; if (pxCount % 2 != 0){ //Padding io.ReadByte(); bytes--; } break; } } y++; break; /** End marker **/ case 0x05: endmarker = true; break; /** Leave next rows transparent **/ case 0x09: y += count; continue; } } } private Color[] Data; public int Width { get; internal set; } public int Height { get; internal set; } protected void Init(int width, int height) { this.Width = width; this.Height = height; Data = new Color[Width * Height]; } public Color GetPixel(int x, int y) { return Data[(y * Width) + x]; } public void SetPixel(int x, int y, Color color) { Data[(y * Width) + x] = color; } public Texture2D GetTexture(GraphicsDevice device) { DecodeIfRequired(); if (PixelCache == null) { var mip = !Parent.WallStyle && FSOEnvironment.Enable3D && FSOEnvironment.EnableNPOTMip; var tc = FSOEnvironment.TexCompress; if (Width * Height > 0) { var w = Math.Max(1, Width); var h = Math.Max(1, Height); if (mip && TextureUtils.OverrideCompression(w, h)) tc = false; if (tc) { PixelCache = new Texture2D(device, ((w+3)/4)*4, ((h+3)/4)*4, mip, SurfaceFormat.Dxt5); if (mip) TextureUtils.UploadDXT5WithMips(PixelCache, w, h, device, Data); else PixelCache.SetData(TextureUtils.DXT5Compress(Data, w, h).Item1); } else { PixelCache = new Texture2D(device, w, h, mip, SurfaceFormat.Color); if (mip) TextureUtils.UploadWithMips(PixelCache, device, Data); else PixelCache.SetData(this.Data); } } else { PixelCache = new Texture2D(device, Math.Max(1, Width), Math.Max(1, Height), mip, SurfaceFormat.Color); PixelCache.SetData(new Color[] { Color.Transparent }); } PixelCache.Tag = new TextureInfo(PixelCache, Width, Height); if (!IffFile.RETAIN_CHUNK_DATA) Data = null; } return PixelCache; } } }