private static fcTL Read_fcTL(string png, int seq, int delay) { BinaryReader br = new BinaryReader(new FileStream(png, FileMode.Open)); br.BaseStream.Position = 0x10; fcTL fctl = new fcTL(); fctl.length = BitConverter.GetBytes(26).Reverse().ToArray(); fctl.id = Encoding.ASCII.GetBytes(new char[] { 'f', 'c', 'T', 'L' }); fctl.sequence_numer = BitConverter.GetBytes(seq).Reverse().ToArray(); fctl.width = br.ReadBytes(4); fctl.height = br.ReadBytes(4); fctl.x_offset = BitConverter.GetBytes(0); fctl.y_offset = BitConverter.GetBytes(0); fctl.delay_num = BitConverter.GetBytes((ushort)delay).Reverse().ToArray(); fctl.delay_den = BitConverter.GetBytes((ushort)1000).Reverse().ToArray(); fctl.dispose_op = 1; fctl.blend_op = 0; List <Byte> stream = new List <byte>(); stream.AddRange(fctl.id); stream.AddRange(fctl.sequence_numer); stream.AddRange(fctl.width); stream.AddRange(fctl.height); stream.AddRange(fctl.x_offset); stream.AddRange(fctl.y_offset); stream.AddRange(fctl.delay_num); stream.AddRange(fctl.delay_den); stream.Add(fctl.dispose_op); stream.Add(fctl.blend_op); fctl.crc = Helper.CRC32.Calculate(stream.ToArray()); stream.Clear(); br.Close(); return(fctl); }
private static void Write(string apng, byte[] signature, IHDR ihdr, acTL actl, byte[] idat, fcTL[] fctl, fdAT[] fdat, IEND iend) { BinaryWriter bw = new BinaryWriter(new FileStream(apng, FileMode.Create)); bw.Write(signature); bw.Write(ihdr.length); bw.Write(ihdr.id); bw.Write(ihdr.width); bw.Write(ihdr.height); bw.Write(ihdr.depth); bw.Write(ihdr.colour_type); bw.Write(ihdr.compression); bw.Write(ihdr.filter); bw.Write(ihdr.interlace); bw.Write(ihdr.crc); bw.Write(actl.length); bw.Write(actl.id); bw.Write(actl.num_frames); bw.Write(actl.num_plays); bw.Write(actl.crc); bw.Write(fctl[0].length); bw.Write(fctl[0].id); bw.Write(fctl[0].sequence_numer); bw.Write(fctl[0].width); bw.Write(fctl[0].height); bw.Write(fctl[0].x_offset); bw.Write(fctl[0].y_offset); bw.Write(fctl[0].delay_num); bw.Write(fctl[0].delay_den); bw.Write(fctl[0].dispose_op); bw.Write(fctl[0].blend_op); bw.Write(fctl[0].crc); bw.Write(idat); for (int i = 0; i < fdat.Length; i++) { bw.Write(fctl[i + 1].length); bw.Write(fctl[i + 1].id); bw.Write(fctl[i + 1].sequence_numer); bw.Write(fctl[i + 1].width); bw.Write(fctl[i + 1].height); bw.Write(fctl[i + 1].x_offset); bw.Write(fctl[i + 1].y_offset); bw.Write(fctl[i + 1].delay_num); bw.Write(fctl[i + 1].delay_den); bw.Write(fctl[i + 1].dispose_op); bw.Write(fctl[i + 1].blend_op); bw.Write(fctl[i + 1].crc); bw.Write(fdat[i].length); bw.Write(fdat[i].id); bw.Write(fdat[i].sequence_number); bw.Write(fdat[i].data); bw.Write(fdat[i].crc); } bw.Write(iend.length); bw.Write(iend.id); bw.Write(iend.crc); bw.Flush(); bw.Close(); }
private static fcTL Read_fcTL(string png, int seq, int delay) { BinaryReader br = new BinaryReader(new FileStream(png, FileMode.Open)); br.BaseStream.Position = 0x10; fcTL fctl = new fcTL(); fctl.length = BitConverter.GetBytes(26).Reverse().ToArray(); fctl.id = Encoding.ASCII.GetBytes(new char[] { 'f', 'c', 'T', 'L' }); fctl.sequence_numer = BitConverter.GetBytes(seq).Reverse().ToArray(); fctl.width = br.ReadBytes(4); fctl.height = br.ReadBytes(4); fctl.x_offset = BitConverter.GetBytes(0); fctl.y_offset = BitConverter.GetBytes(0); fctl.delay_num = BitConverter.GetBytes((ushort)delay).Reverse().ToArray(); fctl.delay_den = BitConverter.GetBytes((ushort)1000).Reverse().ToArray(); fctl.dispose_op = 1; fctl.blend_op = 0; List<Byte> stream = new List<byte>(); stream.AddRange(fctl.id); stream.AddRange(fctl.sequence_numer); stream.AddRange(fctl.width); stream.AddRange(fctl.height); stream.AddRange(fctl.x_offset); stream.AddRange(fctl.y_offset); stream.AddRange(fctl.delay_num); stream.AddRange(fctl.delay_den); stream.Add(fctl.dispose_op); stream.Add(fctl.blend_op); fctl.crc = Helper.CRC32.Calculate(stream.ToArray()); stream.Clear(); br.Close(); return fctl; }
/// <summary> /// PNGファイルを展開します。 /// </summary> /// <param name="path">展開するPNGファイルのパスを設定します。</param> /// <param name="imageList">展開した画像データを格納するコレクションを設定します。</param> /// <returns>画像のサイズとアニメーションの再生回数を返します。</returns> public (int width, int height, int times) Decode(string path, List <FrameModel> imageList) { var chunks = new List <string>(); var images = new List <byte[]>(); var frames = new List <fcTL>(); byte[] buffer; bool hasIHDR = false; bool hasPLTE = false; bool hasACTL = false; bool hasIEND = false; var ImageHeader = new IHDR(); var AnimationControl = new acTL(); bool isIdatFrame = false; int sequence = 0; bool isFCTL = false; imageList.Clear(); using (var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read)) { // PNGヘッダをスキップ stream.Seek(Signature.Length, SeekOrigin.Begin); var offset = Signature.Length; while (!hasIEND) { // チャンクを読み取り var length = ReadUINT(stream); buffer = ReadBytes(stream, (int)(length + 4)); var type = Encoding.ASCII.GetString(buffer.Take(4).ToArray()); var crc = ReadUINT(stream); // CRCチェック var calcCRC = new CRC32(); calcCRC.SlurpBlock(buffer, 0, (int)(length + 4)); if (crc != (uint)calcCRC.Crc32Result) { throw CreateEx(type, offset, "CRCが一致していません"); } switch (type) { case "IHDR": // イメージヘッダ読み取り if (hasIHDR) { throw CreateEx(type, offset, "チャンクが複数存在しています"); } if (chunks.Count > 0) { throw CreateEx(type, offset, "チャンクの位置が不正です"); } ImageHeader.Width = ReadUINT(buffer, 4); ImageHeader.Height = ReadUINT(buffer, 8); ImageHeader.BitDepth = buffer[12]; ImageHeader.ColorType = (ColorType)buffer[13]; ImageHeader.CompressionMethod = buffer[14]; ImageHeader.FilterMethod = buffer[15]; ImageHeader.InterlaceMethod = (InterlaceMethod)buffer[16]; if (ImageHeader.Width == 0 || ImageHeader.Height == 0) { throw CreateEx(type, offset, "画像のサイズが不正です"); } if (!Enum.IsDefined(typeof(ColorType), ImageHeader.ColorType) || !AllowBitDepth.ContainsKey(ImageHeader.ColorType) || !AllowBitDepth[ImageHeader.ColorType].Contains(ImageHeader.BitDepth)) { throw CreateEx(type, offset, "カラーモードとビット深度の組み合わせが不正です"); } if (ImageHeader.CompressionMethod != 0) { throw CreateEx(type, offset, "圧縮手法の値が不正です"); } if (ImageHeader.FilterMethod != 0) { throw CreateEx(type, offset, "フィルター手法の値が不正です"); } if (!Enum.IsDefined(typeof(InterlaceMethod), ImageHeader.InterlaceMethod)) { throw CreateEx(type, offset, "インターレース手法の値が不正です"); } hasIHDR = true; break; case "PLTE": // パレット読み取り if (hasPLTE) { throw CreateEx(type, offset, "チャンクが複数存在しています"); } // // パレット読み込み処理 // hasPLTE = true; break; case "IDAT": // イメージデータ読み取り var idat = buffer.Skip(4).Take(buffer.Length - 4).ToArray(); if (chunks[chunks.Count - 1] != type) { if (images.Count != 0) { throw CreateEx(type, offset, "非連続なチャンクが存在しています"); } images.Add(idat); if (hasACTL && frames.Count == 1) { isIdatFrame = true; } } else { images[images.Count - 1] = images[images.Count - 1].Concat(idat).ToArray(); } if (hasACTL) { isFCTL = false; } break; case "acTL": // アニメーションコントロール読み取り if (hasACTL) { throw CreateEx(type, offset, "チャンクが複数存在しています"); } if (images.Count != 0) { throw CreateEx(type, offset, "チャンクの位置が不正です"); } AnimationControl.NumFrames = ReadUINT(buffer, 4); AnimationControl.NumPlays = ReadUINT(buffer, 8); if (AnimationControl.NumFrames == 0) { throw CreateEx(type, offset, "アニメーションフレーム数が0です"); } hasACTL = true; break; case "fcTL": // フレームコントロール読み取り if (isFCTL) { throw CreateEx(type, offset, "フレームコントロールが連続しています"); } var fctl = new fcTL() { SequenceNumber = ReadUINT(buffer, 4), Width = ReadUINT(buffer, 8), Height = ReadUINT(buffer, 12), XOffset = ReadUINT(buffer, 16), YOffset = ReadUINT(buffer, 20), DelayNum = ReadWORD(buffer, 24), DelayDen = ReadWORD(buffer, 26), DisposeOp = (DisposeOp)buffer[28], BlendOp = (BlendOp)buffer[29] }; if (frames.Count == 0 && sequence != 0) { throw CreateEx(type, offset, "最初のチャンクの位置が不正です"); } if (fctl.SequenceNumber != sequence) { throw CreateEx(type, offset, "シーケンス番号が不正です"); } if (frames.Count == 0 && (fctl.Width != ImageHeader.Width || fctl.Height != ImageHeader.Height || fctl.XOffset != 0 || fctl.YOffset != 0)) { throw CreateEx(type, offset, "1枚目のアニメーションフレームのサイズが不正です"); } if (fctl.Width == 0 || fctl.Height == 0 || fctl.XOffset + fctl.Width > ImageHeader.Width || fctl.YOffset + fctl.Height > ImageHeader.Height) { throw CreateEx(type, offset, "アニメーションフレームのサイズが不正です"); } if (!Enum.IsDefined(typeof(DisposeOp), fctl.DisposeOp)) { throw CreateEx(type, offset, "フレーム描画後の処理方法の値が不正です"); } if (!Enum.IsDefined(typeof(BlendOp), fctl.BlendOp)) { throw CreateEx(type, offset, "フレーム描画方法の値が不正です"); } if (fctl.DelayDen == 0) { fctl.DelayDen = 100; } frames.Add(fctl); sequence++; isFCTL = true; break; case "fdAT": // フレームデータ読み取り if (!isFCTL) { throw CreateEx(type, offset, "対応するフレームコントロールが存在しません"); } var seq = ReadUINT(buffer, 4); if (seq != sequence) { throw CreateEx(type, offset, "シーケンス番号が不正です"); } var fdat = buffer.Skip(8).Take(buffer.Length - 8).ToArray(); if (chunks[chunks.Count - 1] != type) { images.Add(fdat); } else { images[images.Count - 1] = images[images.Count - 1].Concat(fdat).ToArray(); } sequence++; isFCTL = false; break; case "IEND": hasIEND = true; break; } chunks.Add(type); offset += buffer.Length + 8; } } // 必須チャンクチェック if (!hasIHDR || !hasIEND || (!hasPLTE && ImageHeader.ColorType == ColorType.Palette)) { throw new InvalidOperationException("必須チャンクが存在しません"); } // 不要チャンクチェック if (hasPLTE && (ImageHeader.ColorType == ColorType.Grayscale || ImageHeader.ColorType == ColorType.GrayscaleAlpha)) { throw new InvalidOperationException("不要なチャンクが存在しています"); } // フレーム数チェック if (hasACTL && (AnimationControl.NumFrames != frames.Count || AnimationControl.NumFrames != images.Count - (isIdatFrame ? 0 : 1))) { throw new InvalidOperationException("アニメーションフレーム数が不正です"); } // イメージデータチェック if (images.Count == 0) { throw new InvalidOperationException("イメージデータが存在しません"); } // イメージデータ展開 if (hasACTL) { // APNG for (var i = 0; i < frames.Count; i++) { imageList.Add(new FrameModel() { XOffset = (int)frames[i].XOffset, YOffset = (int)frames[i].YOffset, Width = (int)frames[i].Width, Height = (int)frames[i].Height, Delay = frames[i].DelayNum * 1000 / frames[i].DelayDen, Dispose = frames[i].DisposeOp == DisposeOp.APNG_DISPOSE_OP_NONE ? FrameModel.DisposeMode.None : frames[i].DisposeOp == DisposeOp.APNG_DISPOSE_OP_BACKGROUND ? FrameModel.DisposeMode.Background : FrameModel.DisposeMode.Previous, Blend = frames[i].BlendOp == BlendOp.APNG_BLEND_OP_SOURCE ? FrameModel.BlendMode.Normal : FrameModel.BlendMode.AlphaBlending, Data = Unfilter(ZlibStream.UncompressBuffer(images[i + (isIdatFrame ? 0 : 1)]), (int)frames[i].Width, (int)frames[i].Height, ImageHeader.ColorType) }); } } else { // PNG imageList.Add(new FrameModel() { Width = (int)ImageHeader.Width, Height = (int)ImageHeader.Height, Data = Unfilter(ZlibStream.UncompressBuffer(images[0]), (int)ImageHeader.Width, (int)ImageHeader.Height, ImageHeader.ColorType) }); } return(width : (int)ImageHeader.Width, height : (int)ImageHeader.Height, times : (int)(hasACTL ? AnimationControl.NumPlays : 0));
public IHDR(fcTL fctl) { }