Creating and manipulating animation using APNG fileformat
Contents
[
Hide
]
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.
1) Export APNG animation to animated GIF
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.png")) | |
{ | |
// Save to the same format | |
image.Save(dataDir + "result.png"); | |
// Export to the other animated format | |
image.Save(dataDir + "result.gif", new GifOptions()); | |
} | |
File.Delete(dataDir + "result.png"); | |
File.Delete(dataDir + "result.gif"); |
![]() |
![]() |
---|---|
Input image | Result image |
2) Create an animation from multi-page Tiff file
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.tiff")) | |
{ | |
// Setting up the default frame duration | |
image.Save(dataDir + "result.png", new ApngOptions() { DefaultFrameTime = 500 }); // 500 ms | |
image.Save(dataDir + "result2.png", new ApngOptions() { DefaultFrameTime = 250 }); // 250 ms | |
} | |
File.Delete(dataDir + "result.png"); | |
File.Delete(dataDir + "result2.png"); |
Result animation:
3) Create APNG animation from single-page image
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); |
![]() |
![]() |
---|---|
Source image | Created animation |
4) Create APNG animation using vector graphics
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
Created from graphics animation: