以编程方式创建动画位图图像

Gyö*_*zeg 5 c# animation drawing gdi+ winforms

我想System.Drawing.Bitmap“手动”创建一个包含动画的实例。

Bitmap要创建的实例应满足以下条件:

  • 它是一个动画(image.FrameDimensionsLists有一个时间维度)
  • 它有多个框架 ( image.GetFrameCount(dimension) > 1)
  • 我可以获得帧之间的延迟 ( image.GetPropertyItem(0x5100).Value)

我很确定可以通过一些 WinApi 创建这样的图像。这也是 GIF 解码器的实际作用。

我知道我可以播放动画,如果我通过做手工有任何来源的框架,但我希望做一个兼容的方式:如果我能产生这样的位图,我可以简单地使用它的一个ButtonLabelPictureBox或者任何其他现有控件,内置控件ImageAnimator也可以自动处理它。

大多数类似主题都建议将帧转换为动画 GIF;然而,这不是一个好的解决方案,因为它不能处理真彩色和半透明(例如 APNG 动画)。

更新:经过一些探索,我了解到我可以使用WIC实现解码器;但是,我不想在 Windows 中注册新的解码器,它使用 COM,如果可能,我想避免使用 COM。更不用说最后我会有一个IWICBitmapSource,我仍然需要将其转换为Bitmap.

更新 2:我设置了赏金。如果您可以实现以下方法,您就是赢家:

public void Bitmap CreateAnimation(Bitmap[] frames, int[] delays)
{
    // Any WinApi is allowed. WIC is also allowed, but not preferred.
    // Creating an animated GIF is not an acceptable answer. What if frames are from an APNG?
}
Run Code Online (Sandbox Code Playgroud)

Han*_*ant 5

    public void Bitmap CreateAnimation(Bitmap[] frames, int[] delays)
Run Code Online (Sandbox Code Playgroud)

像这样对预期的实现设置严格的限制并不是很明智。利用 TIFF 图像格式在技术上是可行的,它能够存储多个帧。然而,它们不是基于时间的,只有 GIF 编解码器支持。需要一个额外的参数,以便在需要渲染下一个图像时更新控件。像这样:

    public static Image CreateAnimation(Control ctl, Image[] frames, int[] delays) {
        var ms = new System.IO.MemoryStream();
        var codec = ImageCodecInfo.GetImageEncoders().First(i => i.MimeType == "image/tiff");

        EncoderParameters encoderParameters = new EncoderParameters(2);
        encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag, (long)EncoderValue.MultiFrame);
        encoderParameters.Param[1] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, (long)EncoderValue.CompressionLZW);
        frames[0].Save(ms, codec, encoderParameters);

        encoderParameters = new EncoderParameters(1);
        encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag, (long)EncoderValue.FrameDimensionPage);
        for (int i = 1; i < frames.Length; i++) {
            frames[0].SaveAdd(frames[i], encoderParameters);
        }
        encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag, (long)EncoderValue.Flush);
        frames[0].SaveAdd(encoderParameters);

        ms.Position = 0;
        var img = Image.FromStream(ms);
        Animate(ctl, img, delays);
        return img;
    }
Run Code Online (Sandbox Code Playgroud)

Animate() 方法需要一个 Timer 来选择下一帧并更新控件:

    private static void Animate(Control ctl, Image img, int[] delays) {
        int frame = 0;
        var tmr = new Timer() { Interval = delays[0], Enabled = true };
        tmr.Tick += delegate {
            frame++;
            if (frame >= delays.Length) frame = 0;
            img.SelectActiveFrame(FrameDimension.Page, frame);
            tmr.Interval = delays[frame];
            ctl.Invalidate();
        };
        ctl.Disposed += delegate { tmr.Dispose(); };
    }
Run Code Online (Sandbox Code Playgroud)

示例用法:

    public Form1() {
        InitializeComponent();
        pictureBox1.Image = CreateAnimation(pictureBox1,
            new Image[] { Properties.Resources.Frame1, Properties.Resources.Frame2, Properties.Resources.Frame3 },
            new int[] { 1000, 2000, 300 });
    }
Run Code Online (Sandbox Code Playgroud)

一个更聪明的方法是完全放弃返回值要求,这样你就不必生成 TIFF。只需使用带有Action<Image>参数的 Animate() 方法即可更新控件的属性。但不是你所要求的。