public void ProcessUpdates(SpriteFrame frameToUpdate, Bitmap bmpWithChanges) { //update the bitmap, if frameToUpdate.PendingChanges is true frameToUpdate.ProcessUpdates(bmpWithChanges); Sprite spr = frameToUpdate.ParentSprite; List<byte[]> SpriteFrameDataArrays = new List<byte[]>(); //update the SprBitmapOffset since changes will change the size of the //FrameRawData when it gets written to an SPR. The game depends on having //the exact offsets to the SPR data. int offset = 0; for (int i = 0; i < spr.Frames.Count; i++) { SpriteFrame sf = spr.Frames[i]; offset += sf.FrameRawData.Length; //we depend on short-circuit evaluation here.If i isn't less then the Frames.Count - 1, //we'll end up with an out-of-bounds exception. We can't just test for PendingChanges because //changes in one SpriteFrame will affect offsets in others, not in itself. if ((i < spr.Frames.Count - 1) && (spr.Frames[i + 1].SprBitmapOffset != offset)) { spr.Frames[i + 1].SprBitmapOffset = offset; //this.PendingChanges = true; foreach (DataRow dr in spr.Frames[i + 1].GameSetDataRows) { dr.BeginEdit(); dr[9] = offset.ToString(); dr.AcceptChanges(); //calls EndEdit() implicitly } sf.PendingChanges = false; } //Killing two birds with one for loop. See below. SpriteFrameDataArrays.Add(sf.FrameRawData); } //convert the List<byte[]> to a byte[] int lastSize = 0; byte[] newSprData = new byte[offset]; //offset now equals the total size of all the frames foreach (byte[] b in SpriteFrameDataArrays) { Buffer.BlockCopy(b, 0, newSprData, lastSize, b.Length); lastSize += b.Length; } this._sprData = newSprData; //using (MemoryStream newSprData = new MemoryStream(offset)) //{ // newSprData.SetLength(offset); // int count = 0; // int len = 0; // foreach (SpriteFrame sf in spr.Frames) // { // try // { // //newSprData.SetLength(newSprData.Length + sf.FrameRawData.Length); // newSprData.Write(sf.FrameRawData, (int) newSprData.Position, sf.FrameRawData.Length); // count++; // len += sf.FrameRawData.Length; // } // catch(Exception e) // { // Debugger.Break(); // } // } // this._sprData = newSprData.GetBuffer(); //} //this.BuildSpr(); //this._sprData = BuildSpr(); //OnSpriteObjectChanged(EventArgs.Empty); }
public static void SprStreamToSpriteFrame(SpriteFrame sf, Stream stream) { sf.SprBitmapOffset = (int) stream.Position; //Read Header byte[] frame_size_bytes = new byte[8]; stream.Read(frame_size_bytes, 0, 8); int sprSize = BitConverter.ToInt32(frame_size_bytes, 0); sf.Width = BitConverter.ToInt16(frame_size_bytes, 4); sf.Height = BitConverter.ToInt16(frame_size_bytes, 6); sf.PixelSize = sf.Height * sf.Width; sf.FrameBmpData = new byte[sf.PixelSize]; sf.FrameRawData = new byte[sf.PixelSize]; //initialize it to an unused transparent-pixel-marker //todo: Verify 0xff is/isn't used in any of the other sprites sf.FrameBmpData = Enumerable.Repeat<byte>(0xff, sf.PixelSize).ToArray(); sf.FrameRawData = Enumerable.Repeat<byte>(0xff, sf.PixelSize).ToArray(); Buffer.BlockCopy(frame_size_bytes, 0, sf.FrameRawData, 0, 8); int pixelsToSkip = 0; int bytesRead = 8; //start after the header info byte pixel; for (int y = 0; y < sf.Height; ++y) { for (int x = 0; x < sf.Width; ++x) { if (pixelsToSkip != 0) //only if we've previously identified transparent bits { if (pixelsToSkip >= sf.Width - x) //greater than one line { pixelsToSkip -= (sf.Width - x); // skip to next line break; } x += pixelsToSkip; //skip reading the indicated amount of bytes for transparent pictures pixelsToSkip = 0; } try { pixel = Convert.ToByte(stream.ReadByte()); sf.FrameRawData[bytesRead] = pixel; bytesRead++; } catch { //got -1 for EOF byte[] resize = sf.FrameRawData; Array.Resize<byte>(ref resize, bytesRead); sf.FrameRawData = resize; return; } if (pixel < 0xf8)//MIN_TRANSPARENT_CODE) //normal pixel { sf.FrameBmpData[sf.Width * y + x] = pixel; } else if (pixel == 0xf8)//MANY_TRANSPARENT_CODE) { pixel = Convert.ToByte(stream.ReadByte()); pixelsToSkip = pixel - 1; sf.FrameRawData[bytesRead] = pixel; bytesRead++; } else //f9,fa,fb,fc,fd,fe,ff { pixelsToSkip = 256 - pixel - 1; // skip (neg al) pixels } }//end inner for }//end outer for byte[] resizeMe = sf.FrameRawData; Array.Resize<byte>(ref resizeMe, bytesRead); sf.FrameRawData = resizeMe; }
public static void FrameBmpToSpr(SpriteFrame sf, ColorPalette pal) { byte palColorByte; byte transparentByte = 0xf8; int transparentByteCount = 0; int realOffset = 8; //since our array offset is unaware of the SPR header data byte[] indexedData = new byte[sf.PixelSize + 4]; // todo: will have to recalculate height/width if bitmap size changes byte[] width = BitConverter.GetBytes((short) sf.Width); byte[] height = BitConverter.GetBytes((short) sf.Height); /************************************************************************** * BitConverter is required, rather than Convert.ToByte(), so we can * get the full 16- or 32-bit representations of the values. This is also * why Height and Width are both cast to short, to ensure we get a 16-bit * representation of each value to match the binary's file format of: * ____________________________________________________________ * | 4 byte Size | 2 byte Width | 2 byte Height | byte[] data | **************************************************************************/ int seek_pos = 4; //first four bytes are for SprSize, at the end of the function Buffer.BlockCopy(width, 0, indexedData, seek_pos, width.Length); seek_pos += width.Length; Buffer.BlockCopy(height, 0, indexedData, seek_pos, height.Length); seek_pos += height.Length; List<Color> Palette = new List<Color>(); foreach (Color c in pal.Entries) { Palette.Add(c); } ////BuildBitmap8bppIndexed() may be called to save the ////current image before making changes. So we build ////a 32-bit BMP with current FrameData so it can be used ////below and to build this SPR to return to the caller. //if (sf.ImageBmp == null) // FrameSprToBmp(sf); for (int y = 0; y < sf.ImageBmp.Height; ++y) { for (int x = 0; x < sf.ImageBmp.Width; ++x) { Color pixel = sf.ImageBmp.GetPixel(x, y); var pixARGB = pixel.ToArgb(); palColorByte = Convert.ToByte(Palette.FindIndex(c => c == Color.FromArgb(pixARGB))); if (palColorByte > 0xf8) //0xf9 - 0xff are transparent transparentByteCount++; // Once we hit a non-zero pixel, we need to write out the transparent pixel marker // and the count of transparent pixels. We then write out the current non-zero pixel. // The second expression after || below is to check if we're on the last pixel of the // image. If so, and the final pixels were colored, there won't be a next pixel to be // below 0xf8 so we need to write it out anyway. bool lastByte = (x == (sf.Width - 1) && (y == (sf.Height - 1))); if (palColorByte <= 0xf8 || lastByte) { if (transparentByteCount > 0) { // Write 0xf8[dd] where [dd] is transparent byte count, unless the // number of transparent bytes is 6 or less, then just use the other // codes below. Seems like the devs were pretty ruthless in trying to // save disk space back in 1997. if (transparentByteCount > 7) { indexedData[realOffset] = transparentByte; realOffset++; indexedData[realOffset] = Convert.ToByte(transparentByteCount); realOffset++; transparentByteCount = 0; } else { //less than 8 and 7kaa cuts down on file size by just writing one byte //transparentByteCount = 2: 0xfe //transparentByteCount = 3: 0xfd //transparentByteCount = 4: 0xfc //transparentByteCount = 5: 0xfb //transparentByteCount = 6: 0xfa //transparentByteCount = 7: 0xf9 indexedData[realOffset] = Convert.ToByte(0xff - (transparentByteCount - 1)); realOffset++; transparentByteCount = 0; } } //there is no other byte to write out if (!lastByte) { indexedData[realOffset] = palColorByte; realOffset++; } } }//end inner for }//end outer for //subtract four because the int32 size in the header is exclusive of those bytes used for the int32 size byte[] size = BitConverter.GetBytes(realOffset - 4); if (size.Length > 4) { string error = $"SPR size must be Int32! Size for {sf.ParentSprite.SpriteId}'s SpriteFrame at offset {realOffset} is {size.ToString()}"; Trace.WriteLine(error); throw new Exception(error); } Buffer.BlockCopy(size, 0, indexedData, 0, size.Length); //Since FrameData is set to ((Width * Height) + 4), its length will //be based on the real pixels, not the "compressed" length with //the transparent pixels. This makes it impossible to calculate the //offsets of the next frames in the sprite to build a new game set. Array.Resize<byte>(ref indexedData, realOffset); sf.FrameRawData = indexedData; }
public static Bitmap FrameSprToBmp(SpriteFrame sf, ColorPalette pal) { int idx; Bitmap bmp = new Bitmap(sf.Width, sf.Height); FastBitmap fbmp = new FastBitmap(bmp); fbmp.LockImage(); for (int y = 0; y < sf.Height; y++) { for (int x = 0; x < sf.Width; x++) { idx = sf.FrameBmpData[y * sf.Width + x]; Color pixel = pal.Entries[idx]; fbmp.SetPixel(x, y, pixel); } } fbmp.UnlockImage(); Color transparentByte = Color.FromArgb(0xff); bmp.MakeTransparent(transparentByte); return bmp; }
public SpriteFrame AddFrame(SpriteFrame sf = null) { if (sf == null) { sf = new SpriteFrame(); this.Frames.Add(sf); return sf; } this.Frames.Add(sf); return sf; }
/// <summary> /// Opens an SPR file and creates a <see cref="SpriteResource"/> object for it /// </summary> /// <param name="filepath">The absolute path to the SPR file to open</param> /// <returns>The newly-created <see cref="Sprite"/></returns> /// <remarks> /// The original game code for reading SPR files can be found <code>ResourceDb::init_imported()</code> /// in src/ORESDB.cpp around line 72. The <code>resName</code> will be "sprite\\NAME.SPR". (There's /// no need to follow the call into <code>File::file_open()</code> in OFILE.cpp. Though the files are /// well-structured, they are considered FLAT by 7KAA. /// </remarks> public void LoadSprite(string filepath) { if (this.ActivePalette == null) throw new Exception("Cannot load a Sprite if the ActivePalette is null."); //cant use the property here or we'll fire the event before we've finished loading Sprite spr = new Sprite(this.ActivePalette); //this._activeSprite = new Sprite(this.ActivePalette); using (FileStream spritestream = File.OpenRead(filepath)) { while (spritestream.Position < spritestream.Length) { SpriteFrame sf = new SpriteFrame(spr); SprDataHandlers.SprStreamToSpriteFrame(sf, spritestream); sf.ImageBmp = SprDataHandlers.FrameSprToBmp(sf, this.ActivePalette); spr.Frames.Add(sf); } } spr.Resource.FileName = Path.GetFileName(filepath); spr.SpriteId = Path.GetFileNameWithoutExtension(filepath); DataView dv = this.ActiveGameSet.GetSpriteDataView(spr.SpriteId); spr.SetSpriteDataView(dv); this.ActiveSprite = spr; }