/// <summary> /// Writes GIF metadata into extension blocks. /// </summary> protected void WriteMetadata() { Blocks.RemoveAll(b => b as GIFCommentExtension != null); foreach (var prop in Properties) { if (prop.Tag == ExifTag.GIFComment) { var gifComment = prop as GIFComment; if (gifComment != null) { var block = new GIFCommentExtension(); var interop = gifComment.Interoperability; if (interop.Count == 0) { continue; } var subBlockCount = (interop.Count - 1) / 255 + 1; block.Data = new byte[subBlockCount][]; int offset = 0; for (int i = 0; i < subBlockCount; i++) { int count = Math.Min(255, interop.Data.Length - offset); block.Data[i] = new byte[count]; Array.Copy(interop.Data, offset, block.Data[i], 0, count); offset += count; } var insertBefore = gifComment.InsertBefore; int index = insertBefore == null ? -1 : Blocks.IndexOf(insertBefore); if (index == -1) { index = Blocks[Blocks.Count - 1].Separator == GIFSeparator.Terminator ? Blocks.Count - 1 : Blocks.Count; } Blocks.Insert(index, block); index++; } } } }
/// <summary> /// Initializes a new instance of the <see cref="GIFFile"/> class from the /// specified data stream. /// </summary> /// <param name="stream">A stream that contains image data.</param> /// <param name="encoding">The encoding to be used for text metadata when the source encoding is unknown.</param> protected internal GIFFile(MemoryStream stream, System.Text.Encoding encoding) { Format = ImageFileFormat.GIF; Blocks = new List <GIFBlock>(); var conv = BitConverterEx.LittleEndian; stream.Seek(0, SeekOrigin.Begin); // version var versionBytes = Utility.GetStreamBytes(stream, 6); Version = Encoding.ASCII.GetString(versionBytes, 3, 3); // screen descriptor ScreenWidth = conv.ToUInt16(Utility.GetStreamBytes(stream, 2), 0); ScreenHeight = conv.ToUInt16(Utility.GetStreamBytes(stream, 2), 0); var sdByte = (byte)stream.ReadByte(); // global color table flag in bit 7 HasGCT = (sdByte & (1 << 7)) != 0; // color resolution in bits 6, 5, 4 int b6 = (sdByte & (1 << 6)); int b5 = (sdByte & (1 << 5)); int b4 = (sdByte & (1 << 4)); ColorResolution = (byte)(((b6 | b5 | b4) >> 4) + 1); // global color table sorted flag in bit 3 IsGCTSorted = (sdByte & (1 << 3)) != 0; // global color table size bits 2, 1, 0 int b2 = (sdByte & (1 << 2)); int b1 = (sdByte & (1 << 1)); int b0 = (sdByte & (1 << 0)); SizeOfGCT = (byte)((b2 | b1 | b0) + 1); BackcolorIndex = (byte)stream.ReadByte(); PixelAspectRatio = (byte)stream.ReadByte(); // global color table GCT = ReadColorTable(stream, HasGCT ? MathEx.Power2(SizeOfGCT) : 0); // Search and read blocks until we reach the end of file. while (stream.Position != stream.Length) { int val = stream.ReadByte(); if (val == -1) { break; } byte separator = (byte)val; if (separator == 0x3B) { // end of image Blocks.Add(new GIFTerminator()); break; } else if (separator == 0x2C) { // image descriptor block var block = new GIFImageDescriptor(); block.Left = conv.ToUInt16(Utility.GetStreamBytes(stream, 2), 0); block.Top = conv.ToUInt16(Utility.GetStreamBytes(stream, 2), 0); block.Width = conv.ToUInt16(Utility.GetStreamBytes(stream, 2), 0); block.Height = conv.ToUInt16(Utility.GetStreamBytes(stream, 2), 0); var idByte = (byte)stream.ReadByte(); // local color table flag in bit 7 block.HasLCT = (idByte & (1 << 7)) != 0; // interlaced flag in bit 6 block.IsInterlaced = (idByte & (1 << 6)) != 0; // local color table sorted flag in bit 5 block.IsLCTSorted = (idByte & (1 << 5)) != 0; // reserved value in bits 4, 3 int id4 = (idByte & (1 << 4)); int id3 = (idByte & (1 << 3)); block.Reserved = (byte)((id4 | id3) >> 3); // local color table size bits 2, 1, 0 int id2 = (idByte & (1 << 2)); int id1 = (idByte & (1 << 1)); int id0 = (idByte & (1 << 0)); block.SizeOfLCT = (byte)((id2 | id1 | id0) + 1); // local color table block.LCT = ReadColorTable(stream, block.HasLCT ? MathEx.Power2(block.SizeOfLCT) : 0); // raster data block.LZWMinimumCodeSize = (byte)stream.ReadByte(); block.ImageData = ReadDataBlock(stream); Blocks.Add(block); } else if (separator == 0x21) { // extension block val = stream.ReadByte(); if (val == -1) { break; } var label = (byte)val; if (label == 0xF9) { // graphic control extension var size = stream.ReadByte(); var block = new GIFGraphicControlExtension(); var geByte = (byte)stream.ReadByte(); // reserved value in bits 7, 6, 5 int ge7 = (geByte & (1 << 7)); int ge6 = (geByte & (1 << 6)); int ge5 = (geByte & (1 << 5)); block.Reserved = (byte)((ge7 | ge6 | ge5) >> 5); // disposal method in bits 4, 3, 2 int ge4 = (geByte & (1 << 4)); int ge3 = (geByte & (1 << 3)); int ge2 = (geByte & (1 << 2)); block.DisposalMethod = (byte)((ge4 | ge3 | ge2) >> 2); // user input flag in bit 1 block.UserInputFlag = (geByte & (1 << 1)) != 0; // transparent color flag in bit 0 block.TransparentColorFlag = (geByte & (1 << 0)) != 0; block.DelayTime = conv.ToUInt16(Utility.GetStreamBytes(stream, 2), 0); block.TransparentColorIndex = (byte)stream.ReadByte(); var term = stream.ReadByte(); Blocks.Add(block); } else if (label == 0xFE) { // comment extension var block = new GIFCommentExtension(); // comment data block.Data = ReadDataBlock(stream); Blocks.Add(block); } else if (label == 0x01) { // plain text extension var block = new GIFPlainTextExtension(); var size = stream.ReadByte(); block.Left = conv.ToUInt16(Utility.GetStreamBytes(stream, 2), 0); block.Top = conv.ToUInt16(Utility.GetStreamBytes(stream, 2), 0); block.Width = conv.ToUInt16(Utility.GetStreamBytes(stream, 2), 0); block.Height = conv.ToUInt16(Utility.GetStreamBytes(stream, 2), 0); block.CellWidth = (byte)stream.ReadByte(); block.CellHeight = (byte)stream.ReadByte(); block.ForegroundColorIndex = (byte)stream.ReadByte(); block.BackgroundColorIndex = (byte)stream.ReadByte(); // plain text data block.Data = ReadDataBlock(stream); Blocks.Add(block); } else if (label == 0xFF) { // application extension var block = new GIFApplicationExtension(); var size = stream.ReadByte(); block.ApplicationIdentifier = Utility.GetStreamBytes(stream, 8); block.AuthenticationCode = Utility.GetStreamBytes(stream, 3); // application data block.Data = ReadDataBlock(stream); Blocks.Add(block); } else { // unkown extension block var block = new GIFExtensionBlock(label); block.Data = ReadDataBlock(stream); Blocks.Add(block); } } else { // unknown block throw new NotValidGIFFileException(string.Format("Unkown GIF block separator: 0x{0:X}", separator)); } } // insert a terminator if it doesn't exist if (Blocks[Blocks.Count - 1].Separator != GIFSeparator.Terminator) { Blocks.Add(new GIFTerminator()); } // process metadata ReadMetadata(); }