using System; using System.Linq; using Microsoft.Xna.Framework.Graphics; using FSO.Files.Utils; using System.IO; using Microsoft.Xna.Framework; using FSO.Common.Utils; using FSO.Common.Rendering; using FSO.Common; namespace FSO.Files.Formats.IFF.Chunks { /// /// This chunk type holds a number of paletted sprites that may have z-buffer and/or alpha channels. /// public class SPR2 : IffChunk { public SPR2Frame[] Frames = new SPR2Frame[0]; public uint DefaultPaletteID; public bool SpritePreprocessed; private bool _ZAsAlpha; public bool ZAsAlpha { get { return _ZAsAlpha; } set { if (value && !_ZAsAlpha) { foreach (var frame in Frames) { if (frame.Decoded && frame.PixelData != null) frame.CopyZToAlpha(); } } _ZAsAlpha = value; } } private int _FloorCopy; public int FloorCopy { get { return _FloorCopy; } set { if (value > 0 && _FloorCopy == 0) { foreach (var frame in Frames) { if (frame.Decoded && frame.PixelData != null) { if (value == 1) frame.FloorCopy(); if (value == 2) frame.FloorCopyWater(); } } } _FloorCopy = value; } } /// /// Reads a SPR2 chunk from a stream. /// /// An Iff instance. /// A Stream object holding a SPR2 chunk. public override void Read(IffFile iff, Stream stream) { using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN)) { var version = io.ReadUInt32(); uint spriteCount = 0; if (version == 1000) { spriteCount = io.ReadUInt32(); DefaultPaletteID = io.ReadUInt32(); var offsetTable = new uint[spriteCount]; for (var i = 0; i < spriteCount; i++) { offsetTable[i] = io.ReadUInt32(); } Frames = new SPR2Frame[spriteCount]; for (var i = 0; i < spriteCount; i++) { var frame = new SPR2Frame(this); io.Seek(SeekOrigin.Begin, offsetTable[i]); var guessedSize = ((i + 1 < offsetTable.Length) ? offsetTable[i + 1] : (uint)stream.Length) - offsetTable[i]; frame.Read(version, io, guessedSize); Frames[i] = frame; } } else if (version == 1001) { DefaultPaletteID = io.ReadUInt32(); spriteCount = io.ReadUInt32(); Frames = new SPR2Frame[spriteCount]; for (var i = 0; i < spriteCount; i++) { var frame = new SPR2Frame(this); frame.Read(version, io, 0); Frames[i] = frame; } } } } public override bool Write(IffFile iff, Stream stream) { using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN)) { if (IffFile.TargetTS1) { io.WriteUInt32(1000); uint length = 0; if (Frames != null) length = (uint)Frames.Length; io.WriteUInt32(length); DefaultPaletteID = Frames?.FirstOrDefault()?.PaletteID ?? DefaultPaletteID; io.WriteUInt32(DefaultPaletteID); // begin offset table var offTableStart = stream.Position; for (int i = 0; i < length; i++) io.WriteUInt32(0); //filled in later var offsets = new uint[length]; int offInd = 0; if (Frames != null) { foreach (var frame in Frames) { offsets[offInd++] = (uint)stream.Position; frame.Write(io, true); } } io.Seek(SeekOrigin.Begin, offTableStart); foreach (var off in offsets) io.WriteUInt32(off); io.Seek(SeekOrigin.End, 0); } else { io.WriteUInt32(1001); io.WriteUInt32(DefaultPaletteID); if (Frames == null) io.WriteUInt32(0); else { io.WriteUInt32((uint)Frames.Length); foreach (var frame in Frames) { frame.Write(io, false); } } } return true; } } public void CopyZToAlpha() { foreach (var frame in Frames) { frame.CopyZToAlpha(); } } public override void Dispose() { if (Frames == null) return; foreach (var frame in Frames) { var palette = ChunkParent.Get(frame.PaletteID); if (palette != null) palette.References--; } } } /// /// The frame (I.E sprite) of a SPR2 chunk. /// public class SPR2Frame : ITextureProvider, IWorldTextureProvider { public Color[] PixelData; public byte[] ZBufferData; public byte[] PalData; private WeakReference ZCache = new WeakReference(null); private WeakReference PixelCache = new WeakReference(null); private Texture2D PermaRefZ; private Texture2D PermaRefP; public int Width { get; internal set; } public int Height { get; internal set; } public uint Flags { get; internal set; } public ushort PaletteID { get; set; } public ushort TransparentColorIndex { get; internal set; } public Vector2 Position { get; internal set; } private SPR2 Parent; private uint Version; private byte[] ToDecode; public bool Decoded { get { return ToDecode == null; } } public bool ContainsNothing = false; public bool ContainsNoZ = false; public SPR2Frame(SPR2 parent) { this.Parent = parent; } /// /// Reads a SPR2 chunk from a stream. /// /// Version of the SPR2 that this frame belongs to. /// A IOBuffer object used to read a SPR2 chunk. public void Read(uint version, IoBuffer io, uint guessedSize) { Version = version; if (version == 1001) { var spriteVersion = io.ReadUInt32(); var spriteSize = io.ReadUInt32(); if (IffFile.RETAIN_CHUNK_DATA) ReadDeferred(1001, io); else ToDecode = io.ReadBytes(spriteSize); } else { if (IffFile.RETAIN_CHUNK_DATA) ReadDeferred(1000, io); else ToDecode = io.ReadBytes(guessedSize); } } public void ReadDeferred(uint version, IoBuffer io) { this.Width = io.ReadUInt16(); this.Height = io.ReadUInt16(); this.Flags = io.ReadUInt32(); this.PaletteID = io.ReadUInt16(); if (version == 1000 || this.PaletteID == 0 || this.PaletteID == 0xA3A3) { this.PaletteID = (ushort)Parent.DefaultPaletteID; } TransparentColorIndex = io.ReadUInt16(); var y = io.ReadInt16(); var x = io.ReadInt16(); this.Position = new Vector2(x, y); this.Decode(io); } public void DecodeIfRequired(bool z) { if (ToDecode != null && (((this.Flags & 0x02) == 0x02 && z && ZBufferData == null) || (!z && PixelData == null))) { using (IoBuffer buf = IoBuffer.FromStream(new MemoryStream(ToDecode), ByteOrder.LITTLE_ENDIAN)) { ReadDeferred(Version, buf); } if (TimedReferenceController.CurrentType == CacheType.PERMANENT) ToDecode = null; } } public void Write(IoWriter io, bool ts1) { using (var sprStream = new MemoryStream()) { var sprIO = IoWriter.FromStream(sprStream, ByteOrder.LITTLE_ENDIAN); sprIO.WriteUInt16((ushort)Width); sprIO.WriteUInt16((ushort)Height); sprIO.WriteUInt32(Flags); sprIO.WriteUInt16(PaletteID); sprIO.WriteUInt16(TransparentColorIndex); sprIO.WriteUInt16((ushort)Position.Y); sprIO.WriteUInt16((ushort)Position.X); SPR2FrameEncoder.WriteFrame(this, sprIO); var data = sprStream.ToArray(); if (!ts1) { io.WriteUInt32(1001); io.WriteUInt32((uint)data.Length); } io.WriteBytes(data); } } /// /// Decodes this SPR2Frame. /// /// An IOBuffer instance used to read a SPR2Frame. private void Decode(IoBuffer io) { var y = 0; var endmarker = false; var hasPixels = (this.Flags & 0x01) == 0x01; var hasZBuffer = (this.Flags & 0x02) == 0x02; var hasAlpha = (this.Flags & 0x04) == 0x04; var numPixels = this.Width * this.Height; var ow = Width; var fc = Parent.FloorCopy; if (fc > 0) { numPixels += Height; Width++; } if (hasPixels){ this.PixelData = new Color[numPixels]; this.PalData = new byte[numPixels]; } if (hasZBuffer){ this.ZBufferData = new byte[numPixels]; } var palette = Parent.ChunkParent.Get(this.PaletteID); if (palette == null) palette = new PALT() { Colors = new Color[256] }; palette.References++; var transparentPixel = palette.Colors[TransparentColorIndex]; transparentPixel.A = 0; while (!endmarker && io.HasMore) { var marker = io.ReadUInt16(); var command = marker >> 13; var count = marker & 0x1FFF; switch (command) { /** Fill with pixel data **/ case 0x00: var bytes = count; bytes -= 2; var x = 0; while (bytes > 0) { var pxMarker = io.ReadUInt16(); var pxCommand = pxMarker >> 13; var pxCount = pxMarker & 0x1FFF; bytes -= 2; switch (pxCommand) { case 0x01: case 0x02: var pxWithAlpha = pxCommand == 0x02; for (var col = 0; col < pxCount; col++) { var zValue = io.ReadByte(); var pxValue = io.ReadByte(); bytes -= 2; var pxColor = palette.Colors[pxValue]; if (pxWithAlpha) { var alpha = io.ReadByte(); pxColor.A = (byte)(alpha * 8.2258064516129032258064516129032); bytes--; } //this mode draws the transparent colour as solid for some reason. //fixes backdrop theater var offset = (y * Width) + x; this.PixelData[offset] = pxColor; this.PalData[offset] = pxValue; this.ZBufferData[offset] = zValue; x++; } if (pxWithAlpha) { /** Padding? **/ if ((pxCount * 3) % 2 != 0){ bytes--; io.ReadByte(); } } break; case 0x03: for (var col = 0; col < pxCount; col++) { var offset = (y * Width) + x; this.PixelData[offset] = transparentPixel; this.PalData[offset] = (byte)TransparentColorIndex; this.PixelData[offset].A = 0; if (hasZBuffer){ this.ZBufferData[offset] = 255; } x++; } break; case 0x06: for (var col = 0; col < pxCount; col++) { var pxIndex = io.ReadByte(); bytes--; var offset = (y * Width) + x; var pxColor = palette.Colors[pxIndex]; byte z = 0; //not sure if this should happen /*if (pxIndex == TransparentColorIndex) { pxColor.A = 0; z = 255; }*/ this.PixelData[offset] = pxColor; this.PalData[offset] = pxIndex; if (hasZBuffer) { this.ZBufferData[offset] = z; } x++; } if (pxCount % 2 != 0) { bytes--; io.ReadByte(); } break; } } /** If row isnt filled in, the rest is transparent **/ while (x < ow) { var offset = (y * Width) + x; if (hasZBuffer) { this.ZBufferData[offset] = 255; } x++; } break; /** Leave the next count rows in the color channel filled with the transparent color, * in the z-buffer channel filled with 255, and in the alpha channel filled with 0. **/ case 0x04: for (var row = 0; row < count; row++) { for (var col = 0; col < Width; col++) { var offset = ((y+row) * Width) + col; if (hasPixels) { this.PixelData[offset] = transparentPixel; this.PalData[offset] = (byte)TransparentColorIndex; } if (hasAlpha) { this.PixelData[offset].A = 0; } if (hasZBuffer) { ZBufferData[offset] = 255; } } } y += count - 1; break; case 0x05: endmarker = true; break; } y++; } if (!IffFile.RETAIN_CHUNK_DATA) PalData = null; if (Parent.ZAsAlpha) CopyZToAlpha(); if (Parent.FloorCopy == 1) FloorCopy(); if (Parent.FloorCopy == 2) FloorCopyWater(); } /// /// Gets a pixel from this SPR2Frame. /// /// X position of pixel. /// Y position of pixel. /// A Color instance with color of pixel. public Color GetPixel(int x, int y) { return PixelData[(y * Width) + x]; } /// /// Gets a pixel from this SPR2Frame. /// /// X position of pixel. /// Y position of pixel. public void SetPixel(int x, int y, Color color) { PixelData[(y * Width) + x] = color; } /// /// Copies the Z buffer into the current sprite's alpha channel. Used by water tile. /// public void CopyZToAlpha() { for (int i=0; i= 254) ndat[idx] = rep; else ndat[idx] = PixelData[idx]; idx++; } } PixelData = ndat; } public void FloorCopyWater() { if (Width % 2 != 0) { var target = new Color[(Width + 1) * Height]; for (int y = 0; y < Height; y++) { Array.Copy(PixelData, y * Width, target, y * (Width + 1), Width); } PixelData = target; Width += 1; } var ndat = new Color[PixelData.Length]; int hw = (Width) / 2; int hh = (Height) / 2; int idx = 0; var palette = Parent.ChunkParent.Get(this.PaletteID); var transparentPixel = palette.Colors[TransparentColorIndex]; transparentPixel.A = 0; for (int y = 0; y < Height; y++) { for (int x = 0; x < Width; x++) { var dat = PixelData[x + y * Width]; if (dat.PackedValue == 0 || dat.PackedValue == transparentPixel.PackedValue) { if (x < hw) { for (int j = x; j < Width; j++) { var rep = PixelData[j + y * Width]; if (!(rep.PackedValue == 0 || rep.PackedValue == transparentPixel.PackedValue)) { ndat[idx] = rep; break; } } } else { for (int j = x; j >= 0; j--) { var rep = PixelData[j + y * Width]; if (!(rep.PackedValue == 0 || rep.PackedValue == transparentPixel.PackedValue)) { ndat[idx] = rep; break; } } } } else { ndat[idx] = PixelData[idx]; } idx++; } } PixelData = ndat; } /// /// Gets a texture representing this SPR2Frame. /// /// GraphicsDevice instance used for drawing. /// A Texture2D instance holding the texture data. public Texture2D GetTexture(GraphicsDevice device) { return GetTexture(device, true); } private Texture2D GetTexture(GraphicsDevice device, bool onlyThis) { if (ContainsNothing) return null; Texture2D result = null; if (!PixelCache.TryGetTarget(out result) || ((CachableTexture2D)result).BeingDisposed || result.IsDisposed) { DecodeIfRequired(false); if (this.Width == 0 || this.Height == 0) { ContainsNothing = true; return null; } var tc = FSOEnvironment.TexCompress; var mip = FSOEnvironment.Enable3D && (FSOEnvironment.EnableNPOTMip || (Width == 128 && Height == 64)); if (mip && TextureUtils.OverrideCompression(Width, Height)) tc = false; if (tc) { result = new CachableTexture2D(device, ((Width+3)/4)*4, ((Height + 3) / 4) * 4, mip, SurfaceFormat.Dxt5); if (mip) TextureUtils.UploadDXT5WithMips(result, Width, Height, device, this.PixelData); else { var dxt = TextureUtils.DXT5Compress(this.PixelData, this.Width, this.Height); result.SetData(dxt.Item1); } } else { result = new CachableTexture2D(device, this.Width, this.Height, mip, SurfaceFormat.Color); if (mip) TextureUtils.UploadWithMips(result, device, this.PixelData); else result.SetData(this.PixelData); } result.Tag = new TextureInfo(result, Width, Height); PixelCache = new WeakReference(result); if (TimedReferenceController.CurrentType == CacheType.PERMANENT) PermaRefP = result; if (!IffFile.RETAIN_CHUNK_DATA) { PixelData = null; //if (onlyThis && !FSOEnvironment.Enable3D) ZBufferData = null; } } if (TimedReferenceController.CurrentType != CacheType.PERMANENT) TimedReferenceController.KeepAlive(result, KeepAliveType.ACCESS); return result; } public Texture2D TryGetCachedZ() { Texture2D result = null; if (ContainsNothing || ContainsNoZ) return null; if (!ZCache.TryGetTarget(out result) || ((CachableTexture2D)result).BeingDisposed || result.IsDisposed) return null; return result; } /// /// Gets a z-texture representing this SPR2Frame. /// /// GraphicsDevice instance used for drawing. /// A Texture2D instance holding the texture data. public Texture2D GetZTexture(GraphicsDevice device) { return GetZTexture(device, true); } private Texture2D GetZTexture(GraphicsDevice device, bool onlyThis) { Texture2D result = null; if (ContainsNothing || ContainsNoZ) return null; if (!ZCache.TryGetTarget(out result) || ((CachableTexture2D)result).BeingDisposed || result.IsDisposed) { DecodeIfRequired(true); if (this.Width == 0 || this.Height == 0) { ContainsNothing = true; return null; } if (ZBufferData == null) { ContainsNoZ = true; return null; } if (FSOEnvironment.TexCompress) { result = new CachableTexture2D(device, ((Width+3)/4)*4, ((Height+3)/4)*4, false, SurfaceFormat.Alpha8); var tempZ = new byte[result.Width * result.Height]; var dind = 0; var sind = 0; for (int i=0; i(tempZ); } else { result = new CachableTexture2D(device, this.Width, this.Height, false, SurfaceFormat.Alpha8); result.SetData(this.ZBufferData); } ZCache = new WeakReference(result); if (TimedReferenceController.CurrentType == CacheType.PERMANENT) PermaRefZ = result; if (!IffFile.RETAIN_CHUNK_DATA) { //if (!FSOEnvironment.Enable3D) ZBufferData = null; disabled right now til we get a clean way of getting this post-world-texture for ultra lighting if (onlyThis) PixelData = null; } } if (TimedReferenceController.CurrentType != CacheType.PERMANENT) TimedReferenceController.KeepAlive(result, KeepAliveType.ACCESS); return result; } #region IWorldTextureProvider Members public WorldTexture GetWorldTexture(GraphicsDevice device) { var result = new WorldTexture { Pixel = this.GetTexture(device, false) }; result.ZBuffer = this.GetZTexture(device, false); if (!IffFile.RETAIN_CHUNK_DATA) { PixelData = null; if (!FSOEnvironment.Enable3D) ZBufferData = null; } return result; } #endregion public Color[] SetData(Color[] px, byte[] zpx, Rectangle rect) { PixelCache = null; //can't exactly dispose this.. it's likely still in use! ZCache = null; PixelData = px; ZBufferData = zpx; Position = new Vector2(rect.X, rect.Y); Width = rect.Width; Height = rect.Height; Flags = 7; TransparentColorIndex = 255; var colors = SPR2FrameEncoder.QuantizeFrame(this, out PalData); var palt = new Color[256]; int i = 0; foreach (var c in colors) palt[i++] = new Color(c.R, c.G, c.B, (byte)255); return palt; } public void SetPalt(PALT p) { if (this.PaletteID != 0) { var old = Parent.ChunkParent.Get(this.PaletteID); if (old != null) old.References--; } PaletteID = p.ChunkID; p.References++; } } }