Convert APNG files in C#

Overview

The Animated Portable Network Graphics (APNG) file format is an extension to the Portable Network Graphics (PNG) specification. It allows for animated PNG files that work similarly to animated GIF files, while supporting 24-bit images and 8-bit transparency not available for GIFs. It also retains backward compatibility with non-animated PNG files.

Now the APNG file format is available in the Aspose.Imaging. It is implemented in the “Aspose.Imaging.FileFormats.Apng.ApngImage” class. In general, the behavior of this class is the same as that of similar animated formats (like Gif or Webp).

Create APNG image from PNG

using Aspose.Imaging;
using Aspose.Imaging.FileFormats.Apng;
using Aspose.Imaging.FileFormats.Png;
using Aspose.Imaging.ImageOptions;
using Aspose.Imaging.Sources;
using System.IO;
string templatesFolder = @"c:\Users\USER\Downloads\templates\";
string dataDir = templatesFolder;
// Load pixels from source raster image
Size imageSize;
int[] imagePixels;
using (RasterImage sourceImage = (RasterImage)Image.Load(dataDir + "template.png"))
{
imageSize = sourceImage.Size;
imagePixels = sourceImage.LoadArgb32Pixels(sourceImage.Bounds);
}
// Create APNG image and set its pixels
using (ApngImage image = (ApngImage)Image.Create(
new ApngOptions()
{
Source = new FileCreateSource(dataDir + "result.png", false),
ColorType = PngColorType.TruecolorWithAlpha
},
imageSize.Width,
imageSize.Height))
{
image.SaveArgb32Pixels(image.Bounds, imagePixels);
image.Save();
}
File.Delete(dataDir + "result.png");

Export APNG animation to animated GIF

using Aspose.Imaging;
using Aspose.Imaging.ImageOptions;
using System.IO;
string templatesFolder = @"c:\Users\USER\Downloads\templates\";
string dataDir = templatesFolder;
using (Image image = Image.Load(dataDir + "template.apng"))
{
// Export to the other animated format
image.Save(dataDir + "result.gif", new GifOptions());
}
File.Delete(dataDir + "result.gif");

Save to animated APNG image from animated WEBP

using Aspose.Imaging;
using Aspose.Imaging.ImageOptions;
using System.IO;
string templatesFolder = @"c:\Users\USER\Downloads\templates\";
string dataDir = templatesFolder;
using (Image image = Image.Load(dataDir + "template.webp"))
{
// Export to APNG animation with unlimited animation cycles as default
image.Save(dataDir + "result.png", new ApngOptions());
// Setting up animation cycles
image.Save(dataDir + "result2.png", new ApngOptions() { NumPlays = 5 }); // 5 cycles
}
File.Delete(dataDir + "result.png");
File.Delete(dataDir + "result2.png");

Save to animated APNG image from multi-page Tiff file

Create an animated APNG image from single-page image

using Aspose.Imaging;
using Aspose.Imaging.FileFormats.Apng;
using Aspose.Imaging.FileFormats.Png;
using Aspose.Imaging.ImageOptions;
using Aspose.Imaging.Sources;
using System.IO;
string templatesFolder = @"c:\Users\USER\Downloads\templates\";
string dataDir = templatesFolder;
const int AnimationDuration = 1000; // 1 s
const int FrameDuration = 70; // 70 ms
using (RasterImage sourceImage = (RasterImage)Image.Load(dataDir + "template.png"))
{
ApngOptions createOptions = new ApngOptions
{
Source = new FileCreateSource(dataDir + "result.png", false),
DefaultFrameTime = (uint)FrameDuration,
ColorType = PngColorType.TruecolorWithAlpha,
};
using (ApngImage apngImage = (ApngImage)Image.Create(
createOptions,
sourceImage.Width,
sourceImage.Height))
{
int numOfFrames = AnimationDuration / FrameDuration;
int numOfFrames2 = numOfFrames / 2;
apngImage.RemoveAllFrames();
// add first frame
apngImage.AddFrame(sourceImage, FrameDuration);
// add intermediate frames
for (int frameIndex = 1; frameIndex < numOfFrames - 1; ++frameIndex)
{
apngImage.AddFrame(sourceImage, FrameDuration);
ApngFrame lastFrame = (ApngFrame)apngImage.Pages[apngImage.PageCount - 1];
float gamma = frameIndex >= numOfFrames2 ? numOfFrames - frameIndex - 1 : frameIndex;
lastFrame.AdjustGamma(gamma);
}
// add last frame
apngImage.AddFrame(sourceImage, FrameDuration);
apngImage.Save();
}
}
File.Delete(dataDir + "result.png");

Create APNG animation using vector graphics

using Aspose.Imaging;
using Aspose.Imaging.Brushes;
using Aspose.Imaging.FileFormats.Apng;
using Aspose.Imaging.FileFormats.Png;
using Aspose.Imaging.ImageOptions;
using Aspose.Imaging.Sources;
using System.Collections.Generic;
using System.IO;
using Graphics = Aspose.Imaging.Graphics;
string templatesFolder = @"c:\Users\USER\Downloads\templates\";
string dataDir = templatesFolder;
// preparing the animation scene
const int SceneWidth = 400;
const int SceneHeigth = 400;
const uint ActDuration = 1000; // Act duration, in milliseconds
const uint TotalDuration = 4000; // Total duration, in milliseconds
const uint FrameDuration = 50; // Frame duration, in milliseconds
Scene scene = new Scene();
Ellipse ellipse = new Ellipse
{
FillColor = Color.FromArgb(128, 128, 128),
CenterPoint = new PointF(SceneWidth / 2f, SceneHeigth / 2f),
RadiusX = 80,
RadiusY = 80
};
scene.AddObject(ellipse);
Line line = new Line
{
Color = Color.Blue,
LineWidth = 10,
StartPoint = new PointF(30, 30),
EndPoint = new PointF(SceneWidth - 30, 30)
};
scene.AddObject(line);
IAnimation lineAnimation1 = new LinearAnimation(
delegate (float progress)
{
line.StartPoint = new PointF(
30 + (progress * (SceneWidth - 60)),
30 + (progress * (SceneHeigth - 60)));
line.Color = Color.FromArgb(
(int)(progress * 255),
0,
255 - (int)(progress * 255));
})
{
Duration = ActDuration
};
IAnimation lineAnimation2 = new LinearAnimation(
delegate (float progress)
{
line.EndPoint = new PointF(
SceneWidth - 30 - (progress * (SceneWidth - 60)),
30 + (progress * (SceneHeigth - 60)));
line.Color = Color.FromArgb(
255,
(int)(progress * 255),
0);
})
{
Duration = ActDuration
};
IAnimation lineAnimation3 = new LinearAnimation(
delegate (float progress)
{
line.StartPoint = new PointF(
SceneWidth - 30 - (progress * (SceneWidth - 60)),
SceneHeigth - 30 - (progress * (SceneHeigth - 60)));
line.Color = Color.FromArgb(
255 - (int)(progress * 255),
255,
0);
})
{
Duration = ActDuration
};
IAnimation lineAnimation4 = new LinearAnimation(
delegate (float progress)
{
line.EndPoint = new PointF(
30 + (progress * (SceneWidth - 60)),
SceneHeigth - 30 - (progress * (SceneHeigth - 60)));
line.Color = Color.FromArgb(
0,
255 - (int)(progress * 255),
(int)(progress * 255));
})
{
Duration = ActDuration
};
IAnimation fullLineAnimation = new SequentialAnimation() { lineAnimation1, lineAnimation2, lineAnimation3, lineAnimation4 };
IAnimation ellipseAnimation1 = new LinearAnimation(
delegate (float progress)
{
ellipse.RadiusX += progress * 10;
ellipse.RadiusY += progress * 10;
int compValue = (int)(128 + (progress * 112));
ellipse.FillColor = Color.FromArgb(
compValue,
compValue,
compValue);
})
{
Duration = ActDuration
};
IAnimation ellipseAnimation2 = new Delay() { Duration = ActDuration };
IAnimation ellipseAnimation3 = new LinearAnimation(
delegate (float progress)
{
ellipse.RadiusX -= progress * 10;
int compValue = (int)(240 - (progress * 224));
ellipse.FillColor = Color.FromArgb(
compValue,
compValue,
compValue);
})
{
Duration = ActDuration
};
IAnimation ellipseAnimation4 = new LinearAnimation(
delegate (float progress)
{
ellipse.RadiusY -= progress * 10;
int compValue = (int)(16 + (progress * 112));
ellipse.FillColor = Color.FromArgb(
compValue,
compValue,
compValue);
})
{
Duration = ActDuration
};
///////////////////////////
/// Animation
///////////////////////////
IAnimation fullEllipseAnimation = new SequentialAnimation() { ellipseAnimation1, ellipseAnimation2, ellipseAnimation3, ellipseAnimation4 };
scene.Animation = new ParallelAnimation() { fullLineAnimation, fullEllipseAnimation };
// playing the scene on the newly created ApngImage
ApngOptions createOptions = new ApngOptions
{
Source = new FileCreateSource(dataDir + "result.png", false),
ColorType = PngColorType.TruecolorWithAlpha,
};
using (ApngImage image = (ApngImage)Image.Create(createOptions, SceneWidth, SceneHeigth))
{
image.DefaultFrameTime = FrameDuration;
scene.Play(image, TotalDuration);
image.Save();
}
File.Delete(dataDir + "result.png");
//////////////////////
// The graphics scene
public class Scene
{
private readonly List<IGraphicsObject> graphicsObjects = new List<IGraphicsObject>();
public IAnimation Animation { get; set; }
public void AddObject(IGraphicsObject graphicsObject)
{
this.graphicsObjects.Add(graphicsObject);
}
public void Play(ApngImage animationImage, uint totalDuration)
{
uint frameDuration = animationImage.DefaultFrameTime;
uint numFrames = totalDuration / frameDuration;
uint totalElapsed = 0;
for (uint frameIndex = 0; frameIndex < numFrames; frameIndex++)
{
if (this.Animation != null)
{
this.Animation.Update(totalElapsed);
}
ApngFrame frame = animationImage.PageCount == 0 || frameIndex > 0
? animationImage.AddFrame()
: (ApngFrame)animationImage.Pages[0];
Graphics graphics = new Graphics(frame);
graphics.SmoothingMode = SmoothingMode.AntiAlias;
foreach (IGraphicsObject graphicsObject in this.graphicsObjects)
{
graphicsObject.Render(graphics);
}
totalElapsed += frameDuration;
}
}
}
// The graphics object
public interface IGraphicsObject
{
void Render(Graphics graphics);
}
// The line
public class Line : IGraphicsObject
{
public PointF StartPoint { get; set; }
public PointF EndPoint { get; set; }
public float LineWidth { get; set; }
public Color Color { get; set; }
public void Render(Graphics graphics)
{
graphics.DrawLine(new Pen(this.Color, this.LineWidth), this.StartPoint, this.EndPoint);
}
}
// The ellipse
public class Ellipse : IGraphicsObject
{
public Color FillColor { get; set; }
public PointF CenterPoint { get; set; }
public float RadiusX { get; set; }
public float RadiusY { get; set; }
public void Render(Graphics graphics)
{
graphics.FillEllipse(
new SolidBrush(this.FillColor),
this.CenterPoint.X - this.RadiusX,
this.CenterPoint.Y - this.RadiusY,
this.RadiusX * 2,
this.RadiusY * 2);
}
}
/////////////////////////// IAnimation.cs /////////////////////////////
// The animation
public interface IAnimation
{
// The animation duration, in milliseconds.
uint Duration { get; set; }
void Update(uint elapsed);
}
/////////////////////////// LinearAnimation.cs /////////////////////////////
// The linear animation
public class LinearAnimation : IAnimation
{
private readonly AnimationProgressHandler progressHandler;
public delegate void AnimationProgressHandler(float progress);
public LinearAnimation(AnimationProgressHandler progressHandler)
{
if (progressHandler == null)
{
throw new System.ArgumentNullException("progressHandler");
}
this.progressHandler = progressHandler;
}
public uint Duration { get; set; }
public void Update(uint elapsed)
{
if (elapsed <= this.Duration)
{
this.progressHandler.Invoke((float)elapsed / this.Duration);
}
}
}
/////////////////////////// Delay.cs /////////////////////////////
// The simple delay between other animations
public class Delay : IAnimation
{
public uint Duration { get; set; }
public void Update(uint elapsed)
{
// nop
}
}
// The parallel animation processor
public class ParallelAnimation : List<IAnimation>, IAnimation
{
public uint Duration
{
get
{
uint maxDuration = 0;
foreach (IAnimation animation in this)
{
if (maxDuration < animation.Duration)
{
maxDuration = animation.Duration;
}
}
return maxDuration;
}
set
{
throw new System.NotSupportedException();
}
}
public void Update(uint elapsed)
{
foreach (IAnimation animation in this)
{
animation.Update(elapsed);
}
}
}
// The sequential animation processor
class SequentialAnimation : List<IAnimation>, IAnimation
{
public uint Duration
{
get
{
uint summDuration = 0;
foreach (IAnimation animation in this)
{
summDuration += animation.Duration;
}
return summDuration;
}
set
{
throw new System.NotSupportedException();
}
}
public void Update(uint elapsed)
{
uint totalDuration = 0;
foreach (IAnimation animation in this)
{
if (totalDuration > elapsed)
{
break;
}
animation.Update(elapsed - totalDuration);
totalDuration += animation.Duration;
}
}
}