Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating an animated Bitmap image programmatically

I want to create a System.Drawing.Bitmap instance "manually", which contains an animation.

The Bitmap instance to create should meet the following criteria:

  • It is an animation (image.FrameDimensionsLists has a Time dimension)
  • It has multiple frames (image.GetFrameCount(dimension) > 1)
  • I can obtain the delay between the frames (image.GetPropertyItem(0x5100).Value)

I'm pretty sure it is possible to create such an image via some WinApi. This is what the GIF Decoder actually does as well.

I know that I can play the animation if I have the frames from any source by doing it manually, but I want to do it in a compatible way: If I could produce such a Bitmap, I could simply use it on a Button, Label, PictureBox or any other existing control, and the built-in ImageAnimator could also handle it automatically.

Most of the similar topics suggest to convert the frames into an animated GIF; however, this is not a good solution, because it does not handle true color and semi-transparency (eg. APNG animation).

Update: After some exploring I learned that I could implement a decoder using WIC; however, I do not want to register a new decoder in Windows, and it uses COM, which I want to avoid if possible. Not mentioning that at the end I will have an IWICBitmapSource, which I still need to convert to a Bitmap.

Update 2: I have set a bounty. You are the winner if you can implement the following method:

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?
}
like image 399
György Kőszeg Avatar asked Jul 28 '15 11:07

György Kőszeg


People also ask

Can BMP images be animated?

BMP files aren't generally compatible with animation. To create simple animations that can be saved and shared relatively quickly, you might want to consider using the GIF file format instead.

What is a bitmap animation?

A bitmap (also called "raster") graphic is created from rows of different colored pixels that together form an image. In their simplest form, bitmaps have only two colors, with each pixel being either black or white.


1 Answers

    public void Bitmap CreateAnimation(Bitmap[] frames, int[] delays)

Setting strict limits on the expected implementation like that is not very wise. It is technically possible by taking advantage of the TIFF image format, it is capable of storing multiple frames. They are however not time-based, only the GIF codec support that. One extra argument is required so the control can be updated when the next image needs to be rendered. Like this:

    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;
    }

The Animate() method needs a Timer to select the next frame and get the control updated:

    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(); };
    }

Sample usage:

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

A smarter way to go about it is to drop the return value requirement completely so you don't have to generate the TIFF. And just use the Animate() method with an Action<Image> argument to get the control's property updated. But not what you asked for.

like image 61
Hans Passant Avatar answered Oct 18 '22 07:10

Hans Passant