I want to create a System.Drawing.Bitmap
instance "manually", which contains an animation.
The Bitmap
instance to create should meet the following criteria:
image.FrameDimensionsLists
has a Time dimension)image.GetFrameCount(dimension) > 1
)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?
}
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With