diff --git a/source2/Users/Orvid/ImageManipulatorTester/..svnbridge/Test.gif b/source2/Users/Orvid/ImageManipulatorTester/..svnbridge/Test.gif new file mode 100644 index 000000000..08149303f --- /dev/null +++ b/source2/Users/Orvid/ImageManipulatorTester/..svnbridge/Test.gif @@ -0,0 +1 @@ +svn:mime-typeapplication/octet-stream \ No newline at end of file diff --git a/source2/Users/Orvid/ImageManipulatorTester/Form1.cs b/source2/Users/Orvid/ImageManipulatorTester/Form1.cs index 506a6726a..6f88be050 100644 --- a/source2/Users/Orvid/ImageManipulatorTester/Form1.cs +++ b/source2/Users/Orvid/ImageManipulatorTester/Form1.cs @@ -12,6 +12,7 @@ namespace ImageManipulatorTester public partial class Form1 : System.Windows.Forms.Form { private StreamWriter st; + public Form1() { InitializeComponent(); @@ -363,10 +364,51 @@ namespace ImageManipulatorTester System.GC.Collect(); + #region Load Gif + { + FileStream s = new FileStream(Path.GetFullPath("Test.gif"), FileMode.Open); + + t.Start(); + anim = Orvid.Graphics.ImageFormats.GifSupport.Load(s); + t.Stop(); + WriteToLog("Loading a Gif took '" + t.ElapsedMilliseconds.ToString() + " ms'"); + t.Reset(); + + s.Close(); + s.Dispose(); + + Bitmap b = (Bitmap)anim[0]; + GifPictureBox = new PictureBox(); + GifPictureBox.BorderStyle = BorderStyle.FixedSingle; + GifPictureBox.Parent = flowLayoutPanel2; + GifPictureBox.Height = b.Height; + GifPictureBox.Width = b.Width; + GifPictureBox.Image = b; + animpar = new Orvid.Graphics.Shapes.ShapedImage(anim.Width, anim.Height); + anim.Parent = animpar; + animpar.Shapes.Add(anim); + + //time.Interval = anim.TimePerFrame * 4; + //time.Tick += new EventHandler(time_Tick); + //time.Start(); + } + #endregion + st.Flush(); st.Close(); st.Dispose(); } + + private PictureBox GifPictureBox; + private Orvid.Graphics.AnimatedImage anim; + private Orvid.Graphics.Shapes.ShapedImage animpar; + private Timer time = new Timer(); + private void time_Tick(object sender, EventArgs e) + { + animpar.Modified = true; + GifPictureBox.Image = (Bitmap)animpar.Render(); + GifPictureBox.Refresh(); + } } } diff --git a/source2/Users/Orvid/ImageManipulatorTester/Test.gif b/source2/Users/Orvid/ImageManipulatorTester/Test.gif new file mode 100644 index 000000000..2a97f6dbc Binary files /dev/null and b/source2/Users/Orvid/ImageManipulatorTester/Test.gif differ diff --git a/source2/Users/Orvid/Orvid.Graphics/AnimatedImage.cs b/source2/Users/Orvid/Orvid.Graphics/AnimatedImage.cs new file mode 100644 index 000000000..5ef73398a --- /dev/null +++ b/source2/Users/Orvid/Orvid.Graphics/AnimatedImage.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Orvid.Graphics +{ + public class AnimatedImage : Shapes.Shape, IDisposable + { + public Image[] Frames; + private UInt32 curFrameIndex; + private bool loop = false; + public int Height { get { return Frames[0].Height; } } + public int Width { get { return Frames[0].Width; } } + + /// + /// This is in Milli-Seconds + /// + public int TimePerFrame { get; set; } + + public bool Loop + { + get + { + return loop; + } + set + { + loop = value; + } + } + + public UInt32 CurFrameIndex + { + get + { + return curFrameIndex; + } + set + { + SetCurrrentFrameIndex(value); + } + } + + public Image this[UInt32 val] + { + get + { + return GetFrame(val); + } + set + { + SetFrame(val, value); + } + } + + public void SetFrame(UInt32 indx, Image i) + { + if (indx < Frames.Length) + Frames[indx] = i; + throw new Exception("Specified Frame Doesn't Exist!"); + } + + public AnimatedImage() + { + Frames = new Image[0]; + } + + public AnimatedImage(Image[] images) + { + Frames = new Image[images.Length]; + Array.Copy(images, Frames, images.Length); + curFrameIndex = 0; + } + + public Image GetFrame(UInt32 indx) + { + if (indx < Frames.Length) + return Frames[indx]; + throw new Exception("Specified Frame Doesn't Exist!"); + } + + public void AddFrame(Image i) + { + Image[] tmp = new Image[Frames.Length + 1]; + Array.Copy(Frames, tmp, Frames.Length); + tmp[tmp.Length - 1] = i; + Frames = tmp; + curFrameIndex = 1; + } + + public void SetCurrrentFrameIndex(uint v) + { + if (v < Frames.Length - 1) + curFrameIndex = v; + else + throw new Exception("Specified Frame Non-Existant!"); + } + + public override void Draw() + { + Parent.Clear(new Pixel(true)); + Parent.DrawImage(new Vec2(this.X, this.Y), Frames[curFrameIndex]); + + if (curFrameIndex + 2 <= Frames.Length - 1) + { + curFrameIndex++; + curFrameIndex++; + } + else// if (loop) + { + curFrameIndex = 0; + } + + this.Modified = true; + } + + public void Dispose() + { + this.Frames = null; + } + } +} diff --git a/source2/Users/Orvid/Orvid.Graphics/ImageFormats/FormatManager.cs b/source2/Users/Orvid/Orvid.Graphics/ImageFormats/FormatManager.cs index 977b93205..0ec778aae 100644 --- a/source2/Users/Orvid/Orvid.Graphics/ImageFormats/FormatManager.cs +++ b/source2/Users/Orvid/Orvid.Graphics/ImageFormats/FormatManager.cs @@ -15,6 +15,7 @@ namespace Orvid.Graphics.ImageFormats Formats.Add(new VbpImage()); Formats.Add(new JpegImage()); Formats.Add(new TiffImage()); + Formats.Add(new TgaImage()); } } diff --git a/source2/Users/Orvid/Orvid.Graphics/ImageFormats/GifSupport.cs b/source2/Users/Orvid/Orvid.Graphics/ImageFormats/GifSupport.cs new file mode 100644 index 000000000..dd1cbabd2 --- /dev/null +++ b/source2/Users/Orvid/Orvid.Graphics/ImageFormats/GifSupport.cs @@ -0,0 +1,646 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; + +namespace Orvid.Graphics.ImageFormats +{ + public static class GifSupport + { + public static AnimatedImage Load(Stream s) + { + GifDecoder g = new GifDecoder(); + return g.DecodeImage(s); + } + + + // Please note, everything below this + // point was originally from the ImageTools + // Library, available here: + // http://imagetools.codeplex.com/ + // + // This disclaimer was last + // modified on August 7, 2011. + + + + #region Internals + + #region DisposalMethod + /// + /// Specifies, what to do with the last image + /// in an animation sequence. + /// + private enum DisposalMethod : int + { + /// + /// No disposal specified. The decoder is not + /// required to take any action. + /// + Unspecified = 0, + /// + /// Do not dispose. The graphic is to be left in place. + /// + NotDispose = 1, + /// + /// Restore to background color. + /// The area used by the graphic must be restored to + /// the background color. + /// + RestoreToBackground = 2, + /// + /// Restore to previous. The decoder is required to + /// restore the area overwritten by the + /// graphic with what was there prior to rendering the graphic. + /// + RestoreToPrevious = 3 + } + #endregion + + #region GifDecoder + private class GifDecoder + { + private const byte ExtensionIntroducer = 0x21; + private const byte Terminator = 0; + private const byte ImageLabel = 0x2C; + private const byte EndIntroducer = 0x3B; + private const byte ApplicationExtensionLabel = 0xFF; + private const byte CommentLabel = 0xFE; + private const byte ImageDescriptorLabel = 0x2C; + private const byte PlainTextLabel = 0x01; + private const byte GraphicControlLabel = 0xF9; + private AnimatedImage _image; + private Stream _stream; + private GifLogicalScreenDescriptor _logicalScreenDescriptor; + private byte[] _globalColorTable; + private byte[] _currentFrame; + private GifGraphicsControlExtension _graphicsControl; + + + public AnimatedImage DecodeImage(Stream stream) + { + _image = new AnimatedImage(); + + _stream = stream; + _stream.Seek(6, SeekOrigin.Current); + + ReadLogicalScreenDescriptor(); + + if (_logicalScreenDescriptor.GlobalColorTableFlag == true) + { + _globalColorTable = new byte[_logicalScreenDescriptor.GlobalColorTableSize * 3]; + + // Read the global color table from the stream + stream.Read(_globalColorTable, 0, _globalColorTable.Length); + } + + int nextFlag = stream.ReadByte(); + while (nextFlag != 0) + { + if (nextFlag == ImageLabel) + { + ReadFrame(); + } + else if (nextFlag == ExtensionIntroducer) + { + int gcl = stream.ReadByte(); + switch (gcl) + { + case GraphicControlLabel: + ReadGraphicalControlExtension(); + break; + case CommentLabel: + ReadComments(); + break; + case ApplicationExtensionLabel: + Skip(12); + break; + case PlainTextLabel: + Skip(13); + break; + } + } + else if (nextFlag == EndIntroducer) + { + break; + } + nextFlag = stream.ReadByte(); + } + return _image; + } + + private void ReadGraphicalControlExtension() + { + byte[] buffer = new byte[6]; + + _stream.Read(buffer, 0, buffer.Length); + + byte packed = buffer[1]; + + _graphicsControl = new GifGraphicsControlExtension(); + _graphicsControl.DelayTime = BitConverter.ToInt16(buffer, 2); + _graphicsControl.TransparencyIndex = buffer[4]; + _graphicsControl.TransparencyFlag = (packed & 0x01) == 1; + _graphicsControl.DisposalMethod = (DisposalMethod)((packed & 0x1C) >> 2); + } + + private GifImageDescriptor ReadImageDescriptor() + { + byte[] buffer = new byte[9]; + + _stream.Read(buffer, 0, buffer.Length); + + byte packed = buffer[8]; + + GifImageDescriptor imageDescriptor = new GifImageDescriptor(); + imageDescriptor.Left = BitConverter.ToInt16(buffer, 0); + imageDescriptor.Top = BitConverter.ToInt16(buffer, 2); + imageDescriptor.Width = BitConverter.ToInt16(buffer, 4); + imageDescriptor.Height = BitConverter.ToInt16(buffer, 6); + imageDescriptor.LocalColorTableFlag = ((packed & 0x80) >> 7) == 1; + imageDescriptor.LocalColorTableSize = 2 << (packed & 0x07); + imageDescriptor.InterlaceFlag = ((packed & 0x40) >> 6) == 1; + + return imageDescriptor; + } + + private void ReadLogicalScreenDescriptor() + { + byte[] buffer = new byte[7]; + + _stream.Read(buffer, 0, buffer.Length); + + byte packed = buffer[4]; + + _logicalScreenDescriptor = new GifLogicalScreenDescriptor(); + _logicalScreenDescriptor.Width = BitConverter.ToInt16(buffer, 0); + _logicalScreenDescriptor.Height = BitConverter.ToInt16(buffer, 2); + _logicalScreenDescriptor.Background = buffer[5]; + _logicalScreenDescriptor.GlobalColorTableFlag = ((packed & 0x80) >> 7) == 1; + _logicalScreenDescriptor.GlobalColorTableSize = 2 << (packed & 0x07); + } + + private void Skip(int length) + { + _stream.Seek(length, SeekOrigin.Current); + + int flag = 0; + + while ((flag = _stream.ReadByte()) != 0) + { + _stream.Seek(flag, SeekOrigin.Current); + } + } + + private void ReadComments() + { + int flag = 0; + + while ((flag = _stream.ReadByte()) != 0) + { + byte[] buffer = new byte[flag]; + _stream.Read(buffer, 0, flag); + } + } + + private void ReadFrame() + { + GifImageDescriptor imageDescriptor = ReadImageDescriptor(); + + byte[] localColorTable = ReadFrameLocalColorTable(imageDescriptor); + + byte[] indices = ReadFrameIndices(imageDescriptor); + + // Determine the color table for this frame. If there is a local one, use it + // otherwise use the global color table. + byte[] colorTable = localColorTable != null ? localColorTable : _globalColorTable; + + ReadFrameColors(indices, colorTable, imageDescriptor); + + int blockSize = _stream.ReadByte(); + if (blockSize > 0) + { + _stream.Seek(blockSize, SeekOrigin.Current); + } + } + + private byte[] ReadFrameIndices(GifImageDescriptor imageDescriptor) + { + int dataSize = _stream.ReadByte(); + + LZWDecoder lzwDecoder = new LZWDecoder(_stream); + + byte[] indices = lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize); + return indices; + } + + private byte[] ReadFrameLocalColorTable(GifImageDescriptor imageDescriptor) + { + byte[] localColorTable = null; + + if (imageDescriptor.LocalColorTableFlag == true) + { + localColorTable = new byte[imageDescriptor.LocalColorTableSize * 3]; + + _stream.Read(localColorTable, 0, localColorTable.Length); + } + + return localColorTable; + } + + private void ReadFrameColors(byte[] indices, byte[] colorTable, GifImageDescriptor descriptor) + { + int imageWidth = _logicalScreenDescriptor.Width; + int imageHeight = _logicalScreenDescriptor.Height; + + if (_currentFrame == null) + { + _currentFrame = new byte[imageWidth * imageHeight * 4]; + } + + byte[] lastFrame = null; + + if (_graphicsControl != null && + _graphicsControl.DisposalMethod == DisposalMethod.RestoreToPrevious) + { + lastFrame = new byte[imageWidth * imageHeight * 4]; + + Array.Copy(_currentFrame, lastFrame, lastFrame.Length); + } + + int offset = 0, i = 0, index = -1; + + int iPass = 0; // the interlace pass + int iInc = 8; // the interlacing line increment + int iY = 0; // the current interlaced line + int writeY = 0; // the target y offset to write to + + for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++) + { + // Check if this image is interlaced. + if (descriptor.InterlaceFlag) + { + // If so then we read lines at predetermined offsets. + // When an entire image height worth of offset lines has been read we consider this a pass. + // With each pass the number of offset lines changes and the starting line changes. + if (iY >= descriptor.Height) + { + iPass++; + switch (iPass) + { + case 1: + iY = 4; + break; + case 2: + iY = 2; + iInc = 4; + break; + case 3: + iY = 1; + iInc = 2; + break; + } + } + + writeY = iY + descriptor.Top; + + iY += iInc; + } + else + { + writeY = y; + } + + for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++) + { + offset = writeY * imageWidth + x; + + index = indices[i]; + + if (_graphicsControl == null || + _graphicsControl.TransparencyFlag == false || + _graphicsControl.TransparencyIndex != index) + { + _currentFrame[offset * 4 + 0] = colorTable[index * 3 + 0]; + _currentFrame[offset * 4 + 1] = colorTable[index * 3 + 1]; + _currentFrame[offset * 4 + 2] = colorTable[index * 3 + 2]; + _currentFrame[offset * 4 + 3] = (byte)255; + } + + i++; + } + } + + byte[] pixels = new byte[imageWidth * imageHeight * 4]; + + Array.Copy(_currentFrame, pixels, pixels.Length); + _currentFrame = new byte[imageWidth * imageHeight * 4]; + Image frame = new Image(imageWidth, imageHeight); + + int indx = 0; + byte r, g, b, a; + for (uint y = 0; y < frame.Height; y++) + { + for (uint x = 0; x < frame.Width; x++) + { + r = pixels[indx]; + indx++; + g = pixels[indx]; + indx++; + b = pixels[indx]; + indx++; + a = pixels[indx]; + indx++; + frame.SetPixel(x, y, new Pixel(r, g, b, a)); + } + } + pixels = null; + System.GC.Collect(); + _image.AddFrame(frame); + + + if (_graphicsControl != null) + { + if (_graphicsControl.DelayTime > 0) + { + _image.TimePerFrame = _graphicsControl.DelayTime; + } + + if (_graphicsControl.DisposalMethod == DisposalMethod.RestoreToBackground) + { + Image im = new Image(imageWidth, imageHeight); + im.Clear(new Pixel(true)); + _image.AddFrame(im); + _image.Loop = false; + + } + else if (_graphicsControl.DisposalMethod == DisposalMethod.RestoreToPrevious) + { + _image.Loop = true; + } + } + } + } + #endregion + + #region GifImageDescriptor + private sealed class GifImageDescriptor + { + /// + /// Column number, in pixels, of the left edge of the image, + /// with respect to the left edge of the Logical Screen. + /// Leftmost column of the Logical Screen is 0. + /// + public short Left; + /// + /// Row number, in pixels, of the top edge of the image with + /// respect to the top edge of the Logical Screen. + /// Top row of the Logical Screen is 0. + /// + public short Top; + /// + /// Width of the image in pixels. + /// + public short Width; + /// + /// Height of the image in pixels. + /// + public short Height; + /// + /// Indicates the presence of a Local Color Table immediately + /// following this Image Descriptor. + /// + public bool LocalColorTableFlag; + /// + /// If the Local Color Table Flag is set to 1, the value in this field + /// is used to calculate the number of bytes contained in the Local Color Table. + /// + public int LocalColorTableSize; + /// + /// Indicates if the image is interlaced. An image is interlaced + /// in a four-pass interlace pattern. + /// + public bool InterlaceFlag; + } + #endregion + + #region GifLogicalScreenDescriptor + private sealed class GifLogicalScreenDescriptor + { + /// + /// Width, in pixels, of the Logical Screen where the images will + /// be rendered in the displaying device. + /// + public short Width; + /// + /// Height, in pixels, of the Logical Screen where the images will be + /// rendered in the displaying device. + /// + public short Height; + /// + /// Index into the Global Color Table for the Background Color. + /// The Background Color is the color used for those + /// pixels on the screen that are not covered by an image. + /// + public byte Background; + /// + /// Flag indicating the presence of a Global Color Table; + /// if the flag is set, the Global Color Table will immediately + /// follow the Logical Screen Descriptor. + /// + public bool GlobalColorTableFlag; + /// + /// If the Global Color Table Flag is set to 1, + /// the value in this field is used to calculate the number of + /// bytes contained in the Global Color Table. + /// + public int GlobalColorTableSize; + } + #endregion + + #region GifGraphicsControlExtension + private sealed class GifGraphicsControlExtension + { + /// + /// Indicates the way in which the graphic is to be treated after being displayed. + /// + public DisposalMethod DisposalMethod; + /// + /// Indicates whether a transparency index is given in the Transparent Index field. + /// (This field is the least significant bit of the byte.) + /// + public bool TransparencyFlag; + /// + /// The Transparency Index is such that when encountered, the corresponding pixel + /// of the display device is not modified and processing goes on to the next pixel. + /// + public int TransparencyIndex; + /// + /// If not 0, this field specifies the number of hundredths (1/100) of a second to + /// wait before continuing with the processing of the Data Stream. + /// The clock starts ticking immediately after the graphic is rendered. + /// This field may be used in conjunction with the User Input Flag field. + /// + public int DelayTime; + } + #endregion + + #region LZWDecoder + private sealed class LZWDecoder + { + private const int StackSize = 4096; + private const int NullCode = -1; + + private Stream _stream; + + /// + /// Initializes a new instance of the class + /// and sets the stream, where the compressed data should be read from. + /// + /// The stream. where to read from. + /// is null + /// (Nothing in Visual Basic). + public LZWDecoder(Stream stream) + { + _stream = stream; + } + + /// + /// Decodes and uncompresses all pixel indices from the stream. + /// + /// The width of the pixel index array. + /// The height of the pixel index array. + /// Size of the data. + /// The decoded and uncompressed array. + public byte[] DecodePixels(int width, int height, int dataSize) + { + byte[] pixels = new byte[width * height]; + int clearCode = 1 << dataSize; + if (dataSize == Int32.MaxValue) + { + throw new ArgumentOutOfRangeException("dataSize", "Must be less than Int32.MaxValue"); + } + int codeSize = dataSize + 1; + int endCode = clearCode + 1; + int availableCode = clearCode + 2; + #region Jillzhangs Code (Not From Me) see: http://giflib.codeplex.com/ + int code = NullCode; + int old_code = NullCode; + int code_mask = (1 << codeSize) - 1; + int bits = 0; + int[] prefix = new int[StackSize]; + int[] suffix = new int[StackSize]; + int[] pixelStatck = new int[StackSize + 1]; + int top = 0; + int count = 0; + int bi = 0; + int xyz = 0; + int data = 0; + int first = 0; + int inCode = NullCode; + for (code = 0; code < clearCode; code++) + { + prefix[code] = 0; + suffix[code] = (byte)code; + } + + byte[] buffer = null; + while (xyz < pixels.Length) + { + if (top == 0) + { + if (bits < codeSize) + { + if (count == 0) + { + buffer = ReadBlock(); + count = buffer.Length; + if (count == 0) + { + break; + } + bi = 0; + } + data += buffer[bi] << bits; + bits += 8; + bi++; + count--; + continue; + } + code = data & code_mask; + data >>= codeSize; + bits -= codeSize; + if (code > availableCode || code == endCode) + { + break; + } + if (code == clearCode) + { + codeSize = dataSize + 1; + code_mask = (1 << codeSize) - 1; + availableCode = clearCode + 2; + old_code = NullCode; + continue; + } + if (old_code == NullCode) + { + pixelStatck[top++] = suffix[code]; + old_code = code; + first = code; + continue; + } + inCode = code; + if (code == availableCode) + { + pixelStatck[top++] = (byte)first; + code = old_code; + } + while (code > clearCode) + { + pixelStatck[top++] = suffix[code]; + code = prefix[code]; + } + first = suffix[code]; + if (availableCode > StackSize) + { + break; + } + pixelStatck[top++] = suffix[code]; + prefix[availableCode] = old_code; + suffix[availableCode] = first; + availableCode++; + if (availableCode == code_mask + 1 && availableCode < StackSize) + { + codeSize++; + code_mask = (1 << codeSize) - 1; + } + old_code = inCode; + } + top--; + pixels[xyz++] = (byte)pixelStatck[top]; + } + + #endregion + + return pixels; + } + + private byte[] ReadBlock() + { + int blockSize = _stream.ReadByte(); + return ReadBytes(blockSize); + } + + private byte[] ReadBytes(int length) + { + byte[] buffer = new byte[length]; + _stream.Read(buffer, 0, length); + return buffer; + } + } + #endregion + + #endregion + } + +} diff --git a/source2/Users/Orvid/Orvid.Graphics/Orvid.Graphics.csproj b/source2/Users/Orvid/Orvid.Graphics/Orvid.Graphics.csproj index 19dbb5974..b1f1634c1 100644 --- a/source2/Users/Orvid/Orvid.Graphics/Orvid.Graphics.csproj +++ b/source2/Users/Orvid/Orvid.Graphics/Orvid.Graphics.csproj @@ -42,6 +42,7 @@ + @@ -56,6 +57,7 @@ + diff --git a/source2/Users/Orvid/Orvid.Graphics/Shapes/Shape.cs b/source2/Users/Orvid/Orvid.Graphics/Shapes/Shape.cs index a5b2073b1..35664cf98 100644 --- a/source2/Users/Orvid/Orvid.Graphics/Shapes/Shape.cs +++ b/source2/Users/Orvid/Orvid.Graphics/Shapes/Shape.cs @@ -49,7 +49,7 @@ namespace Orvid.Graphics.Shapes } } } - public ShapedImage Parent { get; protected set; } + public ShapedImage Parent { get; set; } public abstract void Draw(); } } diff --git a/source2/Users/Orvid/Orvid.Graphics/Shapes/ShapedImage.cs b/source2/Users/Orvid/Orvid.Graphics/Shapes/ShapedImage.cs index dd2bb7aa5..5e2a3eac8 100644 --- a/source2/Users/Orvid/Orvid.Graphics/Shapes/ShapedImage.cs +++ b/source2/Users/Orvid/Orvid.Graphics/Shapes/ShapedImage.cs @@ -6,8 +6,8 @@ namespace Orvid.Graphics.Shapes { public class ShapedImage : Image { - public bool Modified { get; internal set; } - internal List Shapes = new List(); + public bool Modified { get; set; } + public List Shapes = new List(); public ShapedImage(int width, int height) : base(width, height) {