Added: APNG (animated PNG) export for frames, sprites and morphshapes

This commit is contained in:
Jindra Petřík
2026-02-02 22:43:32 +01:00
parent 3dd01e8961
commit 5ef51beb3d
15 changed files with 119 additions and 3 deletions

View File

@@ -65,6 +65,10 @@ import com.jpexs.helpers.Helper;
import com.jpexs.helpers.Path;
import com.jpexs.helpers.SerializableImage;
import com.jpexs.helpers.utf8.Utf8Helper;
import com.jpexs.images.apng.AnimatedPngDecoder;
import com.jpexs.images.apng.AnimatedPngEncoder;
import com.jpexs.images.apng.data.AnimatedPngData;
import com.jpexs.images.apng.data.AnimationFrameData;
import dev.matrixlab.webp4j.WebPCodec;
import dev.matrixlab.webp4j.animation.AnimatedWebPEncoder;
import dev.matrixlab.webp4j.gif.GifToWebPConfig;
@@ -151,6 +155,9 @@ public class FrameExporter {
case PNG:
fem = FrameExportMode.PNG;
break;
case APNG:
fem = FrameExportMode.APNG;
break;
case GIF:
fem = FrameExportMode.GIF;
break;
@@ -563,6 +570,16 @@ public class FrameExporter {
}, handler).run();
}
break;
case APNG:
for (File foutdir : foutdirs) {
frameImages.reset();
new RetryTask(() -> {
File f = new File(foutdir + File.separator + "frames.png");
makeAnimatedPng(frameImages, swf.frameRate, f, evl);
ret.add(f);
}, handler).run();
}
break;
case BMP:
for (File foutdir : foutdirs) {
frameImages.reset();
@@ -831,6 +848,33 @@ public class FrameExporter {
encoder.finish();
}
public static void makeAnimatedPng(Iterator<BufferedImage> images, float frameRate, File file, EventListener evl) throws IOException {
List<AnimationFrameData> frames = new ArrayList<>();
int delayNumerator = 1000;
int delayDenominator = Math.round(1000 * frameRate);
int width = 0;
int height = 0;
BufferedImage backupImage = null;
while (images.hasNext()) {
BufferedImage img = images.next();
if (img == null) {
break;
}
if (backupImage == null) {
backupImage = img;
}
width = img.getWidth();
height = img.getHeight();
frames.add(new AnimationFrameData(img, delayNumerator, delayDenominator));
}
AnimatedPngData data = new AnimatedPngData(width, height, 0, backupImage, frames);
try (FileOutputStream fos = new FileOutputStream(file)) {
AnimatedPngEncoder.encode(data, fos);
}
}
public static void makeAnimatedWebP(Iterator<BufferedImage> images, float frameRate, File file, EventListener evl) throws IOException {
GifToWebPConfig config = GifToWebPConfig.createLosslessConfig();
List<BufferedImage> allImages = new ArrayList<>();

View File

@@ -349,6 +349,13 @@ public class MorphShapeExporter {
ret.add(file);
}, handler).run();
break;
case APNG:
frameImages.reset();
new RetryTask(() -> {
FrameExporter.makeAnimatedPng(frameImages, PreviewExporter.MORPH_SHAPE_ANIMATION_FRAME_RATE, file, evl);
ret.add(file);
}, handler).run();
break;
case PNG_FRAMES:
case BMP_FRAMES:
case WEBP_FRAMES:

View File

@@ -28,6 +28,10 @@ public enum FrameExportMode {
* PNG - Portable Network Graphics
*/
PNG,
/**
* APNG - Animated Portable Network Graphics
*/
APNG,
/**
* GIF - Graphics Interchange Format
*/

View File

@@ -48,6 +48,10 @@ public enum MorphShapeExportMode {
* PNG individual frames - Portable Network Graphics
*/
PNG_FRAMES(false, true),
/**
* APNG - Animated Portable Network Graphics
*/
APNG(true, false),
/**
* BMP start and end frames - Windows Bitmap
*/

View File

@@ -28,6 +28,10 @@ public enum SpriteExportMode {
* PNG - Portable Network Graphics
*/
PNG,
/**
* APNG - Animated Portable Network Graphics
*/
APNG,
/**
* GIF - Graphics Interchange Format
*/

View File

@@ -95,6 +95,7 @@ public class MorphShapeExportSettings {
switch (mode) {
case PNG_START_END:
case PNG_FRAMES:
case APNG:
return ".png";
case BMP_START_END:
case BMP_FRAMES: