Convert APNG files in Java

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 “com.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

// Example 1. Creating an image and setting its pixels.
import com.aspose.imaging.FileFormat;
import com.aspose.imaging.Image;
import com.aspose.imaging.RasterImage;
import com.aspose.imaging.Size;
import com.aspose.imaging.fileformats.apng.ApngImage;
import com.aspose.imaging.fileformats.png.PngColorType;
import com.aspose.imaging.imageoptions.ApngOptions;
import com.aspose.imaging.sources.FileCreateSource;
// Load pixels from source raster image
Size imageSize;
int[] imagePixels;
try (RasterImage sourceImage = (RasterImage) Image.load("not_animated.png"))
{
imageSize = sourceImage.getSize();
imagePixels = sourceImage.loadArgb32Pixels(sourceImage.getBounds());
}
// Create APNG image and set its pixels
try (ApngOptions options = new ApngOptions())
{
options.setSource(new FileCreateSource("created_apng.png", false));
options.setColorType(PngColorType.TruecolorWithAlpha);
try (ApngImage image = (ApngImage)Image.create(options, imageSize.getWidth(), imageSize.getHeight()))
{
image.saveArgb32Pixels(image.getBounds(), imagePixels);
image.save();
}
}
// Check output file format
try (Image image = Image.load("created_apng.png"))
{
assert (image.getFileFormat() == FileFormat.Apng);
assert (image instanceof ApngImage);
}

Export APNG animation to animated GIF

import com.aspose.imaging.Image;
import com.aspose.imaging.fileformats.apng.ApngImage;
import com.aspose.imaging.imageoptions.GifOptions;
try (Image image = Image.load("elephant.png"))
{
// Checking the type of loaded image
assert (image instanceof ApngImage);
// Save to the same format
image.save("elephant_same_format.png");
// Export to the other animated format
image.save("elephant.png.gif", new GifOptions());
}

Save to animated APNG image from animated WEBP

// Example 3. Exporting to APNG file format from other animated format
import com.aspose.imaging.Image;
import com.aspose.imaging.imageoptions.ApngOptions;
try (Image image = Image.load("Animation1.webp"))
{
// Export to APNG animation with unlimited animation cycles as default
image.save("Animation1.webp.png", new ApngOptions());
// Setting up animation cycles
ApngOptions options = new ApngOptions();
options.setNumPlays(5); // 5 cycles
image.save("Animation2.webp.png", options);
}

Save to animated APNG image from multi-page Tiff file

// Example 4. Exporting to APNG file format from other non-animated format
import com.aspose.imaging.Image;
import com.aspose.imaging.imageoptions.ApngOptions;
try (Image image = Image.load("img4.tif"))
{
// Setting up the default frame duration
ApngOptions options = new ApngOptions();
options.setDefaultFrameTime(500); // 500 ms
image.save("img4.tif.500ms.png", options);
options.setDefaultFrameTime(250); // 250 ms
image.save("img4.tif.250ms.png", options);
}

Create an animated APNG image from single-page image

import com.aspose.imaging.Image;
import com.aspose.imaging.RasterImage;
import com.aspose.imaging.fileformats.apng.ApngFrame;
import com.aspose.imaging.fileformats.apng.ApngImage;
import com.aspose.imaging.fileformats.png.PngColorType;
import com.aspose.imaging.imageoptions.ApngOptions;
import com.aspose.imaging.sources.FileCreateSource;
final int AnimationDuration = 1000; // 1 s
final int FrameDuration = 70; // 70 ms
try (RasterImage sourceImage = (RasterImage) Image.load("not_animated.png"))
{
try (ApngOptions createOptions = new ApngOptions())
{
createOptions.setSource(new FileCreateSource("raster_animation.png", false));
createOptions.setDefaultFrameTime(FrameDuration);
createOptions.setColorType(PngColorType.TruecolorWithAlpha);
try (ApngImage apngImage = (ApngImage) Image.create(
createOptions,
sourceImage.getWidth(),
sourceImage.getHeight()))
{
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.getPages()[apngImage.getPageCount() - 1];
float gamma = frameIndex >= numOfFrames2 ? numOfFrames - frameIndex - 1 : frameIndex;
lastFrame.adjustGamma(gamma);
}
// add last frame
apngImage.addFrame(sourceImage, FrameDuration);
apngImage.save();
}
}
}

Create APNG animation using vector graphics

import com.aspose.imaging.Color;
import com.aspose.imaging.Image;
import com.aspose.imaging.PointF;
import com.aspose.imaging.fileformats.apng.ApngImage;
import com.aspose.imaging.fileformats.png.PngColorType;
import com.aspose.imaging.imageoptions.ApngOptions;
import com.aspose.imaging.sources.FileCreateSource;
// preparing the animation scene
final int SceneWidth = 400;
final int SceneHeigth = 400;
// Act duration, in milliseconds
final long ActDuration = 1000;
// Total duration, in milliseconds
final long TotalDuration = 4000;
// Frame duration, in milliseconds
final long FrameDuration = 50;
Scene scene = new Scene();
final Ellipse[] ellipse = { new Ellipse() };
ellipse[0].setFillColor(Color.fromArgb(128, 128, 128));
ellipse[0].setCenterPoint(new PointF(SceneWidth / 2f, SceneHeigth / 2f));
ellipse[0].setRadiusX(80);
ellipse[0].setRadiusY(80);
scene.addObject(ellipse[0]);
final Line[] line = { new Line() };
line[0].setColor(Color.getBlue());
line[0].setLineWidth(10);
line[0].setStartPoint(new PointF(30, 30));
line[0].setEndPoint(new PointF(SceneWidth - 30, 30));
scene.addObject(line[0]);
IAnimation lineAnimation1 = new LinearAnimation(new LinearAnimation.AnimationProgressHandler() {
public void invoke(float progress)
{
line[0].setStartPoint(new PointF(30 + (progress * (SceneWidth - 60)), 30 + (progress * (SceneHeigth - 60))));
line[0].setColor(Color.fromArgb((int)((progress * 255)), 0, 255 - (int)((progress * 255))));
}
});
lineAnimation1.setDuration(ActDuration);
IAnimation lineAnimation2 = new LinearAnimation(new LinearAnimation.AnimationProgressHandler() {
public void invoke(float progress)
{
line[0].setEndPoint(new PointF(SceneWidth - 30 - (progress * (SceneWidth - 60)), 30 + (progress * (SceneHeigth - 60))));
line[0].setColor(Color.fromArgb(255, (int)((progress * 255)), 0));
}
});
lineAnimation2.setDuration(ActDuration);
IAnimation lineAnimation3 = new LinearAnimation(new LinearAnimation.AnimationProgressHandler() {
public void invoke(float progress)
{
line[0].setStartPoint(new PointF(SceneWidth - 30 - (progress * (SceneWidth - 60)), SceneHeigth - 30 - (progress * (SceneHeigth - 60))));
line[0].setColor(Color.fromArgb(255 - (int)((progress * 255)), 255, 0));
}
});
lineAnimation3.setDuration(ActDuration);
IAnimation lineAnimation4 = new LinearAnimation(new LinearAnimation.AnimationProgressHandler() {
public void invoke(float progress)
{
line[0].setEndPoint(new PointF(30 + (progress * (SceneWidth - 60)), SceneHeigth - 30 - (progress * (SceneHeigth - 60))));
line[0].setColor(Color.fromArgb(0, 255 - (int)((progress * 255)), (int)((progress * 255))));
}
});
lineAnimation4.setDuration(ActDuration);
SequentialAnimation fullLineAnimation = new SequentialAnimation();
fullLineAnimation.add(lineAnimation1);
fullLineAnimation.add(lineAnimation2);
fullLineAnimation.add(lineAnimation3);
fullLineAnimation.add(lineAnimation4);
IAnimation ellipseAnimation1 = new LinearAnimation(new LinearAnimation.AnimationProgressHandler() {
public void invoke(float progress)
{
ellipse[0].setRadiusX(ellipse[0].getRadiusX() + (progress * 10));
ellipse[0].setRadiusY(ellipse[0].getRadiusY() + (progress * 10));
int compValue = (int)((128 + (progress * 112)));
ellipse[0].setFillColor(Color.fromArgb(compValue, compValue, compValue));
}
});
ellipseAnimation1.setDuration(ActDuration);
IAnimation ellipseAnimation2 = new Delay();
ellipseAnimation2.setDuration(ActDuration);
IAnimation ellipseAnimation3 = new LinearAnimation(new LinearAnimation.AnimationProgressHandler() {
public void invoke(float progress)
{
ellipse[0].setRadiusX(ellipse[0].getRadiusX() - (progress * 10));
int compValue = (int)((240 - (progress * 224)));
ellipse[0].setFillColor(Color.fromArgb(compValue, compValue, compValue));
}
});
ellipseAnimation3.setDuration(ActDuration);
IAnimation ellipseAnimation4 = new LinearAnimation(new LinearAnimation.AnimationProgressHandler() {
public void invoke(float progress)
{
ellipse[0].setRadiusY(ellipse[0].getRadiusY() - (progress * 10));
int compValue = (int)((16 + (progress * 112)));
ellipse[0].setFillColor(Color.fromArgb(compValue, compValue, compValue));
}
});
ellipseAnimation4.setDuration(ActDuration);
SequentialAnimation fullEllipseAnimation = new SequentialAnimation();
fullEllipseAnimation.add(ellipseAnimation1);
fullEllipseAnimation.add(ellipseAnimation2);
fullEllipseAnimation.add(ellipseAnimation3);
fullEllipseAnimation.add(ellipseAnimation4);
ParallelAnimation tmp0 = new ParallelAnimation();
tmp0.add(fullLineAnimation);
tmp0.add(fullEllipseAnimation);
scene.setAnimation(tmp0);
// playing the scene on the newly created ApngImage
ApngOptions createOptions = new ApngOptions();
try
{
createOptions.setSource(new FileCreateSource("vector_animation.png", false));
createOptions.setColorType(PngColorType.TruecolorWithAlpha);
final ApngImage image = (ApngImage) Image.create(createOptions, SceneWidth, SceneHeigth);
try
/*JAVA: was using*/
{
image.setDefaultFrameTime(FrameDuration);
scene.play(image, TotalDuration);
image.save();
}
finally
{
image.close();
}
}
finally
{
createOptions.close();
}
/////////////////////////// Scene.java /////////////////////////////
import com.aspose.imaging.fileformats.apng.ApngFrame;
import com.aspose.imaging.fileformats.apng.ApngImage;
import java.util.ArrayList;
/**
* <p>
* The graphics scene
* </p>
*/
public class Scene
{
/**
* <p>
* The graphics objects
* </p>
*/
private final java.util.List<IGraphicsObject> graphicsObjects = new ArrayList<IGraphicsObject>();
/**
* <p>
* Gets the animation.
* </p>
*/
public final IAnimation getAnimation()
{
return animation;
}
/**
* <p>
* Sets the animation.
* </p>
*/
public final void setAnimation(IAnimation value)
{
animation = value;
}
private IAnimation animation;
/**
* <p>
* Adds the graphics object.
* </p>
*
* @param graphicsObject The graphics object.
*/
public final void addObject(IGraphicsObject graphicsObject)
{
this.graphicsObjects.add(graphicsObject);
}
/**
* <p>
* Plays scene on the specified animation image.
* </p>
*
* @param animationImage The animation image.
* @param totalDuration The total duration.
*/
public final void play(ApngImage animationImage, long totalDuration)
{
/*UInt32*/
long frameDuration = animationImage.getDefaultFrameTime();
/*UInt32*/
long numFrames = (totalDuration / frameDuration);
/*UInt32*/
long totalElapsed = 0;
for (/*UInt32*/long frameIndex = 0; frameIndex < numFrames; frameIndex++)
{
if (this.getAnimation() != null)
{
this.getAnimation().update(totalElapsed);
}
ApngFrame frame = animationImage.getPageCount() == 0 || frameIndex > 0 ? animationImage.addFrame() : (ApngFrame) animationImage.getPages()[0];
com.aspose.imaging.Graphics graphics = new com.aspose.imaging.Graphics(frame);
//foreach to while statements conversion
for (IGraphicsObject graphicsObject : graphicsObjects)
{
graphicsObject.render(graphics);
}
totalElapsed = (totalElapsed + frameDuration);
}
}
}
/////////////////////////// IGraphicsObject.java /////////////////////////////
/**
* <p>
* The graphics object
* </p>
*/
public interface IGraphicsObject
{
/**
* <p>
* Renders this instance using specified graphics.
* </p>
*
* @param graphics The graphics.
*/
void render(com.aspose.imaging.Graphics graphics);
}
/////////////////////////// Line.java /////////////////////////////
import com.aspose.imaging.Color;
import com.aspose.imaging.Pen;
import com.aspose.imaging.PointF;
/**
* <p>
* The line
* </p>
*/
public class Line implements IGraphicsObject
{
/**
* <p>
* Gets the start point.
* </p>
*
* @return the start point.
*/
public final PointF getStartPoint()
{
return startPoint.Clone();
}
/**
* <p>
* Sets the start point.
* </p>
*
*/
public final void setStartPoint(PointF value)
{
startPoint = value.Clone();
}
private PointF startPoint = new PointF();
/**
* <p>
* Gets the end point.
* </p>
*
*/
public final PointF getEndPoint()
{
return endPoint.Clone();
}
/**
* <p>
* Sets the end point.
* </p>
*
*/
public final void setEndPoint(PointF value)
{
endPoint = value.Clone();
}
private PointF endPoint = new PointF();
/**
* <p>
* Gets the width of the line.
* </p>
*
*/
public final float getLineWidth()
{
return lineWidth;
}
/**
* <p>
* Sets the width of the line.
* </p>
*
* @param value the width of the line.
*/
public final void setLineWidth(float value)
{
lineWidth = value;
}
private float lineWidth;
/**
* <p>
* Gets the color.
* </p>
*
* @return the color.
*/
public final Color getColor()
{
return color;
}
/**
* <p>
* Sets the color.
* </p>
*
* @param value the color.
*/
public final void setColor(Color value)
{
color = value.Clone();
}
private Color color = new Color();
/**
* <p>
* Renders this instance using specified graphics.
* </p>
*
* @param graphics The graphics.
*/
public final void render(com.aspose.imaging.Graphics graphics)
{
graphics.drawLine(new Pen(color, lineWidth), startPoint, endPoint);
}
}
/////////////////////////// Ellipse.java /////////////////////////////
import com.aspose.imaging.Color;
import com.aspose.imaging.PointF;
import com.aspose.imaging.brushes.SolidBrush;
/**
* <p>
* The ellipse
* </p>
*/
public class Ellipse implements IGraphicsObject
{
/**
* <p>
* Gets the color of the fill.
* </p>
*
* @return the color of the fill.
*/
public final Color getFillColor()
{
return fillColor.Clone();
}
/**
* <p>
* Sets the color of the fill.
* </p>
*
* @param value the color of the fill.
*/
public final void setFillColor(Color value)
{
fillColor = value.Clone();
}
private Color fillColor = new Color();
/**
* <p>
* Gets the center point.
* </p>
*
* @return the center point.
*/
public final PointF getCenterPoint()
{
return centerPoint.Clone();
}
/**
* <p>
* Sets the center point.
* </p>
*
* @param value the center point.
*/
public final void setCenterPoint(PointF value)
{
centerPoint = value.Clone();
}
private PointF centerPoint = new PointF();
/**
* <p>
* Gets the radius x.
* </p>
*
* @return the radius x.
*/
public final float getRadiusX()
{
return radiusX;
}
/**
* <p>
* Sets the radius x.
* </p>
*
* @param value the radius x.
*/
public final void setRadiusX(float value)
{
radiusX = value;
}
private float radiusX;
/**
* <p>
* Gets the radius y.
* </p>
*
* @return the radius y.
*/
public final float getRadiusY()
{
return radiusY;
}
/**
* <p>
* Sets the radius y.
* </p>
*
* @param value the radius y.
*/
public final void setRadiusY(float value)
{
radiusY = value;
}
private float radiusY;
/**
* <p>
* Renders this instance using specified graphics.
* </p>
*
* @param graphics The graphics.
*/
@Override
public final void render(com.aspose.imaging.Graphics graphics)
{
graphics.fillEllipse(new SolidBrush(fillColor), centerPoint.getX() - radiusX
, centerPoint.getY() - radiusY, radiusX * 2, radiusY * 2);
}
}
/////////////////////////// IAnimation.java /////////////////////////////
/**
* <p>
* The animation
* </p>
*/
public interface IAnimation
{
/**
* <p>
* Gets the duration.
* </p>
*
* @return the duration.
*/
long getDuration();
/**
* <p>
* Sets the duration.
* </p>
*
* @param value the duration.
*/
void setDuration(long value);
/**
* <p>
* Updates the animation progress.
* </p>
*
* @param elapsed The elapsed time, in milliseconds.
*/
void update(long elapsed);
}
/////////////////////////// LinearAnimation.java /////////////////////////////
/**
* <p>
* The linear animation
* </p>
*/
public class LinearAnimation implements IAnimation
{
/**
* <p>
* The progress handler
* </p>
*/
private final AnimationProgressHandler progressHandler;
/**
* <p>
* Animation progress handler delegate
* </p>
*/
public interface AnimationProgressHandler
{
/**
* <p>
* Animation progress handler delegate
* </p>
*
* @param progress The progress, in [0;1] range.
*/
void invoke(float progress);
}
/**
* <p>
* Initializes a new instance of the {@link LinearAnimation} class.
* </p>
*
* @param progressHandler The progress handler.
* @throws NullPointerException progressHandler is null.
*/
public LinearAnimation(AnimationProgressHandler progressHandler)
{
if (progressHandler == null)
{
throw new NullPointerException("progressHandler");
}
this.progressHandler = progressHandler;
}
/**
* <p>
* Gets the duration.
* </p>
*
* @return the duration.
*/
@Override
public final long getDuration()
{
return duration;
}
/**
* <p>
* Sets the duration.
* </p>
*
* @param value the duration.
*/
@Override
public final void setDuration(long value)
{
duration = value;
}
private long duration;
/**
* <p>
* Updates the animation progress.
* </p>
*
* @param elapsed The elapsed time, in milliseconds.
*/
@Override
public final void update(long elapsed)
{
if (elapsed <= duration)
{
this.progressHandler.invoke((float) elapsed / duration);
}
}
}
/////////////////////////// Delay.java /////////////////////////////
/**
* <p>
* The simple delay between other animations
* </p>
*/
public class Delay implements IAnimation
{
/**
* <p>
* Gets the duration.
* </p>
*
* @return the duration.
*/
@Override
public final long getDuration()
{
return duration;
}
/**
* <p>
* Sets the duration.
* </p>
*
* @param value the duration.
*/
@Override
public final void setDuration(long value)
{
duration = value;
}
private long duration;
/**
* <p>
* Updates the animation progress.
* </p>
*
* @param elapsed The elapsed time, in milliseconds.
*/
@Override
public final void update(long elapsed)
{
// Do nothing
}
}
/////////////////////////// ParallelAnimation.java /////////////////////////////
import java.util.ArrayList;
import java.util.Collection;
/**
* <p>
* The parallel animation processor
* </p>
*/
public class ParallelAnimation extends ArrayList<IAnimation> implements IAnimation
{
/**
* <p>
* Initializes a new instance of the {@link ParallelAnimation} class.
* </p>
*/
public ParallelAnimation()
{
// Do nothing
}
/**
* <p>
* Initializes a new instance of the {@link ParallelAnimation} class.
* </p>
*
* @param animations The animations.
*/
public ParallelAnimation(Collection<IAnimation> animations)
{
super(animations);
}
/**
* <p>
* Gets the duration.
* </p>
*
* @return the duration.
*/
@Override
public final long getDuration()
{
long maxDuration = 0;
for (IAnimation animation : this)
{
if (maxDuration < animation.getDuration())
{
maxDuration = animation.getDuration();
}
}
return maxDuration;
}
/**
* <p>
* Sets the duration.
* </p>
*
* @param value the duration.
*/
@Override
public final void setDuration(long value)
{
throw new UnsupportedOperationException();
}
/**
* <p>
* Updates the animation progress.
* </p>
*
* @param elapsed The elapsed time, in milliseconds.
*/
@Override
public final void update(long elapsed)
{
for (IAnimation animation : this)
{
animation.update(elapsed);
}
}
}
/////////////////////////// SequentialAnimation.java /////////////////////////////
import java.util.ArrayList;
import java.util.Collection;
/**
* <p>
* The sequential animation processor
* </p>
*/
public class SequentialAnimation extends ArrayList<IAnimation> implements IAnimation
{
/**
* <p>
* Initializes a new instance of the {@link SequentialAnimation} class.
* </p>
*/
public SequentialAnimation()
{
// Do nothing
}
/**
* <p>
* Initializes a new instance of the {@link SequentialAnimation} class.
* </p>
*
* @param animations The animations.
*/
public SequentialAnimation(Collection<IAnimation> animations)
{
super(animations);
}
/**
* <p>
* Gets the duration.
* </p>
*
* @return the duration.
*/
@Override
public final long getDuration()
{
/*UInt32*/
long summDuration = 0;
for (IAnimation animation : this)
{
summDuration = summDuration + animation.getDuration();
}
return summDuration;
}
/**
* <p>
* Sets the duration.
* </p>
*
* @param value the duration.
*/
@Override
public final void setDuration(long value)
{
throw new UnsupportedOperationException();
}
/**
* <p>
* Updates the animation progress.
* </p>
*
* @param elapsed The elapsed time, in milliseconds.
*/
@Override
public final void update(long elapsed)
{
long totalDuration = 0;
for (IAnimation animation : this)
{
if (totalDuration > elapsed)
{
break;
}
animation.update((elapsed - totalDuration));
totalDuration = totalDuration + animation.getDuration();
}
}
}