private static ImageFrame ReadImageFrame(Stream stream, byte[] globalColorTable, GraphicControlExtension graphicControlExtension) { var imageDescriptor = ImageDescriptor.Read(stream); var imageFrame = new ImageFrame { ImageDescriptor = imageDescriptor, LocalColorTable = globalColorTable, GraphicControlExtension = graphicControlExtension }; if (imageDescriptor.LocalColorTableFlag) { imageFrame.LocalColorTable = stream.ReadBytes(imageDescriptor.LocalColorTableSize * 3); } imageFrame.ColorDepth = stream.ReadByte(); var lzwDecoder = new LzwDecoder(stream); var imageData = lzwDecoder.DecodeImageData(imageDescriptor.ImageWidth, imageDescriptor.ImageHeight, imageFrame.ColorDepth); ApplicationData.Read(stream); imageFrame.Bitmap = CreateBitmap( imageData, imageFrame.GetPalette(), imageDescriptor.InterlaceFlag, imageDescriptor.ImageWidth, imageDescriptor.ImageHeight); return imageFrame; }
public GifFrame(Stream inputStream, LogicalScreenDescriptor lsd, ColourTable gct, GraphicControlExtension gce, GifFrame previousFrame, GifFrame previousFrameBut1) : base(inputStream, lsd, gct, gce, previousFrame, previousFrameBut1) { }
public void ConvertFrame() { for (var i = 0; i < Frames.Count; i++) { var index = i;//(int)context; var colorTable = colorTables[index]; var localColorTableFlag = (byte)(colorTable == globalColorTable ? 0 : 1); var localColorTableSize = GetColorTableSize(colorTable); byte transparentColorFlag = 0, transparentColorIndex = 0; byte max; var colorIndexes = GetColorIndexes(Frames[index].Texture, scale, colorTable, localColorTableFlag, ref transparentColorFlag, ref transparentColorIndex, out max); var graphicControlExtension = new GraphicControlExtension(4, 0, (byte)Frames[index].DisposalMethod, 0, transparentColorFlag, (ushort)(100 * Frames[index].Delay), transparentColorIndex); var imageDescriptor = new ImageDescriptor(0, 0, Width, Height, localColorTableFlag, 0, 0, 0, localColorTableSize); var minCodeSize = LzwEncoder.GetMinCodeSize(max); var lzw = LzwEncoder.Encode(colorIndexes, minCodeSize); var tableBasedImageData = new TableBasedImageData(minCodeSize, lzw); var tmpBytes = new List <byte>(); tmpBytes.AddRange(graphicControlExtension.GetBytes()); tmpBytes.AddRange(imageDescriptor.GetBytes()); if (localColorTableFlag == 1) { tmpBytes.AddRange(ColorTableToBytes(colorTable, localColorTableSize)); } tmpBytes.AddRange(tableBasedImageData.GetBytes()); encoded.Add(index, tmpBytes); // encodeProgress.Progress++; if (encoded.Count == Frames.Count) { var globalColorTableSize = GetColorTableSize(globalColorTable); var logicalScreenDescriptor = new LogicalScreenDescriptor(Width, Height, 1, 7, 0, globalColorTableSize, 0, 0); var binary = new List <byte>(); binary.AddRange(Encoding.UTF8.GetBytes(header)); binary.AddRange(logicalScreenDescriptor.GetBytes()); binary.AddRange(ColorTableToBytes(globalColorTable, globalColorTableSize)); binary.AddRange(applicationExtension.GetBytes()); binary.AddRange(encoded.OrderBy(j => j.Key).SelectMany(j => j.Value)); binary.Add(0x3B); // GIF Trailer. bytes = binary.ToArray(); // encodeProgress.Completed = true; } // onProgress(encodeProgress); } currentStep++; }
internal static void CheckGraphicControlExtension(GraphicControlExtension ext) { Assert.AreEqual(GraphicControlExtension.ExpectedBlockSize, ext.BlockSize, "ExpectedBlockSize"); Assert.AreEqual(DelayTime, ext.DelayTime, "DelayTime"); Assert.AreEqual(DisposalMethod, ext.DisposalMethod, "DisposalMethod"); Assert.AreEqual(TransparentColourIndex, ext.TransparentColourIndex, "TransparentColourIndex"); Assert.AreEqual(HasTransparentColour, ext.HasTransparentColour, "HasTransparentColour"); Assert.AreEqual(ExpectsUserInput, ext.ExpectsUserInput, "ExpectsUserInput"); }
private static GifFrame DecodeFrame(GraphicControlExtension extension, ImageDescriptor descriptor, byte[] colorIndexes, bool filled, int Width, int Height, Color[] state) { var frame = new GifFrame(); var pixels = state; var transparentIndex = -1; if (extension != null) { frame.Delay = extension.DelayTime / 100f; frame.DisposalMethod = (DisposalMethod)extension.DisposalMethod; if (frame.DisposalMethod == DisposalMethod.RestoreToPrevious) { pixels = state.ToArray(); } if (extension.TransparentColorFlag == 1) { transparentIndex = extension.TransparentColorIndex; } } for (var y = 0; y < descriptor.ImageHeight; y++) { for (var x = 0; x < descriptor.ImageWidth; x++) { var colorIndex = colorIndexes[x + y * descriptor.ImageWidth]; var transparent = colorIndex == transparentIndex; if (transparent && !filled) { continue; } var color = EmptyColor; //transparent ? EmptyColor : colorTable[colorIndex]; var fx = x + descriptor.ImageLeftPosition; var fy = Height - y - 1 - descriptor.ImageTopPosition; // Y-flip pixels[fx + fy * Width] = pixels[fx + fy * Width] = color; } } frame.Texture = new Texture2D(Width, Height); frame.Texture.SetPixels32(pixels); // frame.Texture.Apply(); return(frame); }
static void SetGraphicControlExtension (byte[] gifBytes, ref int byteIndex, ref GifData gifData) { GraphicControlExtension gcEx = new GraphicControlExtension (); // Extension Introducer(1 Byte) // 0x21 gcEx.extensionIntroducer = gifBytes[byteIndex]; byteIndex++; // Graphic Control Label(1 Byte) // 0xf9 gcEx.graphicControlLabel = gifBytes[byteIndex]; byteIndex++; // Block Size(1 Byte) // 0x04 gcEx.blockSize = gifBytes[byteIndex]; byteIndex++; // 1 Byte { // Reserved(3 Bits) // Unused // Disposal Mothod(3 Bits) // 0 (No disposal specified) // 1 (Do not dispose) // 2 (Restore to background color) // 3 (Restore to previous) switch (gifBytes[byteIndex] & 28) { // 0b00011100 case 4: // 0b00000100 gcEx.disposalMethod = 1; break; case 8: // 0b00001000 gcEx.disposalMethod = 2; break; case 12: // 0b00001100 gcEx.disposalMethod = 3; break; default: gcEx.disposalMethod = 0; break; } // User Input Flag(1 Bit) // Unknown // Transparent Color Flag(1 Bit) gcEx.transparentColorFlag = (gifBytes[byteIndex] & 1) == 1; // 0b00000001 byteIndex++; } // Delay Time(2 Bytes) gcEx.delayTime = BitConverter.ToUInt16 (gifBytes, byteIndex); byteIndex += 2; // Transparent Color Index(1 Byte) gcEx.transparentColorIndex = gifBytes[byteIndex]; byteIndex++; // Block Terminator(1 Byte) gcEx.blockTerminator = gifBytes[byteIndex]; byteIndex++; if (gifData.graphicCtrlExList == null) { gifData.graphicCtrlExList = new List<GraphicControlExtension> (); } gifData.graphicCtrlExList.Add (gcEx); }
/// <summary> /// Get disposal method from GraphicControlExtension /// </summary> static ushort GetDisposalMethod (GraphicControlExtension? graphicCtrlEx) { return graphicCtrlEx != null ? graphicCtrlEx.Value.disposalMethod : (ushort) 2; }
/// <summary> /// Get delay seconds from GraphicControlExtension /// </summary> static float GetDelaySec (GraphicControlExtension? graphicCtrlEx) { // Get delay sec from GraphicControlExtension float delaySec = graphicCtrlEx != null ? (float) graphicCtrlEx.Value.delayTime / 100f : 0.1f; // Minimum 0.1 seconds delay (because major browsers have become so...) if (delaySec < 0.1f) { delaySec = 0.1f; } return delaySec; }
/// <summary> /// Get transparent color index from GraphicControlExtension /// </summary> static int GetTransparentIndex (GraphicControlExtension? graphicCtrlEx) { int transparentIndex = -1; if (graphicCtrlEx != null && graphicCtrlEx.Value.transparentColorFlag) { transparentIndex = graphicCtrlEx.Value.transparentColorIndex; } return transparentIndex; }
private void FinishDecoding() { // var globalColorTable = parser.LogicalScreenDescriptor.GlobalColorTableFlag == 1 ? GetUnityColors(parser.GlobalColorTable) : null; //var backgroundColor = globalColorTable?[parser.LogicalScreenDescriptor.BackgroundColorIndex] ?? EmptyColor; GraphicControlExtension gcExtension = null; Width = parser.LogicalScreenDescriptor.LogicalScreenWidth; Height = parser.LogicalScreenDescriptor.LogicalScreenHeight; state = new Color[Width * Height]; filled = false; frames = new List <GifFrame>(); for (var j = 0; j < parser.Blocks.Count; j++) { if (parser.Blocks[j] is GraphicControlExtension) { gcExtension = (GraphicControlExtension)parser.Blocks[j]; } else if (parser.Blocks[j] is ImageDescriptor) { var imageDescriptor = (ImageDescriptor)parser.Blocks[j]; if (imageDescriptor.InterlaceFlag == 1) { throw new NotSupportedException("Interlacing is not supported!"); } // var colorTable = imageDescriptor.LocalColorTableFlag == 1 ? GetUnityColors((ColorTable) parser.Blocks[j + 1]) : globalColorTable; var colorIndexes = decoded[imageDescriptor]; var frame = DecodeFrame(gcExtension, imageDescriptor, colorIndexes, filled, Width, Height, state); frames.Add(frame); //if (frames.Count == 1 && globalColorTable != null) //{ // if (gcExtension == null || gcExtension.TransparentColorFlag == 0 || gcExtension.TransparentColorIndex != parser.LogicalScreenDescriptor.BackgroundColorIndex) // { // backgroundColor = globalColorTable[parser.LogicalScreenDescriptor.BackgroundColorIndex]; // } //} switch (frame.DisposalMethod) { case DisposalMethod.NoDisposalSpecified: case DisposalMethod.DoNotDispose: break; case DisposalMethod.RestoreToBackgroundColor: for (var i = 0; i < state.Length; i++) { state[i] = EmptyColor; } filled = true; break; case DisposalMethod.RestoreToPrevious: // 'state' was already copied before decoding current frame filled = false; break; default: throw new NotSupportedException($"Unknown disposal method: {frame.DisposalMethod}!"); } } } // return new Gif(frames); }
private static GifFrame DecodeFrame(GraphicControlExtension extension, ImageDescriptor descriptor, TableBasedImageData data, bool filled, int width, int height, Color32[] state, Color32[] colorTable) { var colorIndexes = LzwDecoder.Decode(data.ImageData, data.LzwMinimumCodeSize); return(DecodeFrame(extension, descriptor, colorIndexes, filled, width, height, state, colorTable)); }
/// <summary> /// Iterator can be used for large GIF-files in order to display progress bar. /// </summary> public IEnumerable <List <byte> > EncodeIterator(int scale = 1) { if (_free) { if (Frames[0].Texture.width > 256 || Frames[0].Texture.height > 256) { throw new Exception("The free version has maximum supported size 256x256 px. Please consider buying the Full version of Power GIF."); } if (Frames.Count > 20) { throw new Exception("The Free version is limited by 20 frames. Please consider buying the Full version of Power GIF."); } } const string header = "GIF89a"; var width = (ushort)(Frames[0].Texture.width * scale); var height = (ushort)(Frames[0].Texture.height * scale); var globalColorTable = new List <Color32>(); var applicationExtension = new ApplicationExtension(); var bytes = new List <byte>(); var colorTables = new List <Color32> [Frames.Count]; var distinctColors = new Dictionary <int, List <Color32> >(); var manualResetEvent = new ManualResetEvent(false); for (var i = 0; i < Frames.Count; i++) { var frame = Frames[i]; ThreadPool.QueueUserWorkItem(context => { var distinct = frame.Texture.GetPixels32().Distinct().ToList(); lock (distinctColors) { distinctColors.Add((int)context, distinct); if (distinctColors.Count == Frames.Count) { manualResetEvent.Set(); } } }, i); } manualResetEvent.WaitOne(); for (var i = 0; i < Frames.Count; i++) { var colors = distinctColors[i]; var add = colors.Where(j => !globalColorTable.Contains(j)).ToList(); if (globalColorTable.Count + add.Count <= 256) { globalColorTable.AddRange(add); colorTables[i] = globalColorTable; } else if (add.Count <= 256) // Introducing local color table { colorTables[i] = colors; } else { throw new Exception($"Frame #{i} contains more than 256 colors!"); } } ReplaceTransparentColor(ref globalColorTable); for (var i = 0; i < Frames.Count; i++) { var frame = Frames[i]; var colorTable = colorTables[i]; var localColorTableFlag = (byte)(colorTable == globalColorTable ? 0 : 1); var localColorTableSize = GetColorTableSize(colorTable); byte transparentColorFlag = 0, transparentColorIndex = 0; byte max; var colorIndexes = GetColorIndexes(frame.Texture, scale, colorTable, localColorTableFlag, ref transparentColorFlag, ref transparentColorIndex, out max); var graphicControlExtension = new GraphicControlExtension(4, 0, (byte)frame.DisposalMethod, 0, transparentColorFlag, (ushort)(100 * frame.Delay), transparentColorIndex); var imageDescriptor = new ImageDescriptor(0, 0, width, height, localColorTableFlag, 0, 0, 0, localColorTableSize); var minCodeSize = LzwEncoder.GetMinCodeSize(max); var lzw = LzwEncoder.Encode(colorIndexes, minCodeSize); var tableBasedImageData = new TableBasedImageData(minCodeSize, lzw); bytes.Clear(); bytes.AddRange(graphicControlExtension.GetBytes()); bytes.AddRange(imageDescriptor.GetBytes()); if (localColorTableFlag == 1) { bytes.AddRange(ColorTableToBytes(colorTable, localColorTableSize)); } bytes.AddRange(tableBasedImageData.GetBytes()); yield return(bytes); } yield return(new List <byte> { 0x3B }); // GIF Trailer. // Then output GIF header as last iterator element! This way we can build global color table "on fly" instead of expensive building operation. var globalColorTableSize = GetColorTableSize(globalColorTable); var logicalScreenDescriptor = new LogicalScreenDescriptor(width, height, 1, 7, 0, globalColorTableSize, 0, 0); bytes.Clear(); bytes.AddRange(Encoding.UTF8.GetBytes(header)); bytes.AddRange(logicalScreenDescriptor.GetBytes()); bytes.AddRange(ColorTableToBytes(globalColorTable, globalColorTableSize)); bytes.AddRange(applicationExtension.GetBytes()); yield return(bytes); }
/// <summary> /// Encode GIF in multiple threads. /// </summary> public void EncodeParallel(Action <EncodeProgress> onProgress, int scale = 1) // TODO: Refact. { if (_free) { throw new Exception("The Free version doesn't support this feature. Please consider buying the Full version of Power GIF."); } const string header = "GIF89a"; var width = (ushort)(Frames[0].Texture.width * scale); var height = (ushort)(Frames[0].Texture.height * scale); var globalColorTable = new List <Color32>(); var applicationExtension = new ApplicationExtension(); var encoded = new Dictionary <int, List <byte> >(); var encodeProgress = new EncodeProgress { FrameCount = Frames.Count }; var colorTables = new List <Color32> [Frames.Count]; var distinctColors = new Dictionary <int, List <Color32> >(); var manualResetEvent = new ManualResetEvent(false); for (var i = 0; i < Frames.Count; i++) { var frame = Frames[i]; ThreadPool.QueueUserWorkItem(context => { var distinct = frame.Texture.GetPixels32().Distinct().ToList(); lock (distinctColors) { distinctColors.Add((int)context, distinct); if (distinctColors.Count == Frames.Count) { manualResetEvent.Set(); } } }, i); } manualResetEvent.WaitOne(); for (var i = 0; i < Frames.Count; i++) { var colors = distinctColors[i]; var add = colors.Where(j => !globalColorTable.Contains(j)).ToList(); if (globalColorTable.Count + add.Count <= 256) { globalColorTable.AddRange(add); colorTables[i] = globalColorTable; } else if (colors.Count <= 256) // Introduce local color table. { colorTables[i] = colors; } else { onProgress(new EncodeProgress { Completed = true, Exception = new Exception($"Frame #{i} contains more than 256 colors!") }); return; } } ReplaceTransparentColor(ref globalColorTable); for (var i = 0; i < Frames.Count; i++) // Don't use Parallel.For to leave .NET compatibility. { ThreadPool.QueueUserWorkItem(context => { var index = (int)context; var colorTable = colorTables[index]; var localColorTableFlag = (byte)(colorTable == globalColorTable ? 0 : 1); var localColorTableSize = GetColorTableSize(colorTable); byte transparentColorFlag = 0, transparentColorIndex = 0; byte max; var colorIndexes = GetColorIndexes(Frames[index].Texture, scale, colorTable, localColorTableFlag, ref transparentColorFlag, ref transparentColorIndex, out max); var graphicControlExtension = new GraphicControlExtension(4, 0, (byte)Frames[index].DisposalMethod, 0, transparentColorFlag, (ushort)(100 * Frames[index].Delay), transparentColorIndex); var imageDescriptor = new ImageDescriptor(0, 0, width, height, localColorTableFlag, 0, 0, 0, localColorTableSize); var minCodeSize = LzwEncoder.GetMinCodeSize(max); var lzw = LzwEncoder.Encode(colorIndexes, minCodeSize); var tableBasedImageData = new TableBasedImageData(minCodeSize, lzw); var bytes = new List <byte>(); bytes.AddRange(graphicControlExtension.GetBytes()); bytes.AddRange(imageDescriptor.GetBytes()); if (localColorTableFlag == 1) { bytes.AddRange(ColorTableToBytes(colorTable, localColorTableSize)); } bytes.AddRange(tableBasedImageData.GetBytes()); lock (encoded) { encoded.Add(index, bytes); encodeProgress.Progress++; if (encoded.Count == Frames.Count) { var globalColorTableSize = GetColorTableSize(globalColorTable); var logicalScreenDescriptor = new LogicalScreenDescriptor(width, height, 1, 7, 0, globalColorTableSize, 0, 0); var binary = new List <byte>(); binary.AddRange(Encoding.UTF8.GetBytes(header)); binary.AddRange(logicalScreenDescriptor.GetBytes()); binary.AddRange(ColorTableToBytes(globalColorTable, globalColorTableSize)); binary.AddRange(applicationExtension.GetBytes()); binary.AddRange(encoded.OrderBy(j => j.Key).SelectMany(j => j.Value)); binary.Add(0x3B); // GIF Trailer. encodeProgress.Bytes = binary.ToArray(); encodeProgress.Completed = true; } onProgress(encodeProgress); } }, i); } }
/// <summary> /// Iterator can be used for large GIF-files in order to display progress bar. /// </summary> public static IEnumerable <GifFrame> DecodeIterator(byte[] bytes) { var parser = new GifParser(bytes); var blocks = parser.Blocks; var width = parser.LogicalScreenDescriptor.LogicalScreenWidth; var height = parser.LogicalScreenDescriptor.LogicalScreenHeight; var globalColorTable = parser.LogicalScreenDescriptor.GlobalColorTableFlag == 1 ? GetUnityColors(parser.GlobalColorTable) : null; //var backgroundColor = globalColorTable?[parser.LogicalScreenDescriptor.BackgroundColorIndex] ?? EmptyColor; GraphicControlExtension graphicControlExtension = null; var state = new Color32[width * height]; var filled = false; for (var j = 0; j < parser.Blocks.Count; j++) { if (blocks[j] is GraphicControlExtension) { graphicControlExtension = (GraphicControlExtension)blocks[j]; } else if (blocks[j] is ImageDescriptor) { var imageDescriptor = (ImageDescriptor)blocks[j]; if (imageDescriptor.InterlaceFlag == 1) { throw new NotSupportedException("Interlacing is not supported!"); } var colorTable = imageDescriptor.LocalColorTableFlag == 1 ? GetUnityColors((ColorTable)blocks[j + 1]) : globalColorTable; var data = (TableBasedImageData)blocks[j + 1 + imageDescriptor.LocalColorTableFlag]; var frame = DecodeFrame(graphicControlExtension, imageDescriptor, data, filled, width, height, state, colorTable); if (_free) { if (frame.Texture.width > 256 || frame.Texture.height > 256) { throw new Exception("The Free version has maximum supported size 256x256 px. Please consider buying the Full version of Power GIF."); } //if (++frames > 20) throw new Exception("The Free version is limited by 20 frames. Please consider buying the Full version of Power GIF."); } yield return(frame); switch (frame.DisposalMethod) { case DisposalMethod.NoDisposalSpecified: case DisposalMethod.DoNotDispose: break; case DisposalMethod.RestoreToBackgroundColor: for (var i = 0; i < state.Length; i++) { state[i] = EmptyColor; } filled = true; break; case DisposalMethod.RestoreToPrevious: // 'state' was already copied before decoding current frame filled = false; break; default: throw new NotSupportedException($"Unknown disposal method: {frame.DisposalMethod}!"); } } } }