[Patchanim-commit] SF.net SVN: patchanim: [130] trunk/patchanim/src/com/mebigfatguy
Brought to you by:
dbrosius
|
From: <dbr...@us...> - 2008-02-10 04:33:37
|
Revision: 130
http://patchanim.svn.sourceforge.net/patchanim/?rev=130&view=rev
Author: dbrosius
Date: 2008-02-09 20:33:41 -0800 (Sat, 09 Feb 2008)
Log Message:
-----------
start adding support for apngs
Modified Paths:
--------------
trunk/patchanim/src/com/mebigfatguy/patchanim/ExportType.java
trunk/patchanim/src/com/mebigfatguy/patchanim/gui/JPatchAnimFrame.java
trunk/patchanim/src/com/mebigfatguy/patchanim/io/PatchExporter.java
trunk/patchanim/src/com/mebigfatguy/patchanim/main/PatchAnimBundle.java
trunk/patchanim/src/com/mebigfatguy/patchanim/resources.properties
Added Paths:
-----------
trunk/patchanim/src/com/mebigfatguy/apng/
trunk/patchanim/src/com/mebigfatguy/apng/APngEncoder.java
Added: trunk/patchanim/src/com/mebigfatguy/apng/APngEncoder.java
===================================================================
--- trunk/patchanim/src/com/mebigfatguy/apng/APngEncoder.java (rev 0)
+++ trunk/patchanim/src/com/mebigfatguy/apng/APngEncoder.java 2008-02-10 04:33:41 UTC (rev 130)
@@ -0,0 +1,268 @@
+/*
+ * patchanim - A bezier surface patch color blend gif builder
+ * Copyright (C) 2008 Dave Brosius
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package com.mebigfatguy.apng;
+
+import java.awt.image.BufferedImage;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.imageio.ImageIO;
+
+import com.mebigfatguy.patchanim.ExportType;
+
+/**
+ * generates apng files by simply relying on the built in png encoder of ImageIO
+ * and sewing together multiple chunks from the underlying pngs.
+ */
+public class APngEncoder {
+ private static final byte[] HEADER = new byte[] { -119, 80, 78, 71, 13, 10, 26, 10 };
+ private static final int IHDR = 0x49484452;
+ private static final int IDAT = 0x49444154;
+ private static final int IEND = 0x49454E44;
+ private static final int acTL = 0x6163544C;
+ private static final int fcTL = 0x6663544C;
+ private static final int fdAT = 0x66644154;
+
+ private DataOutputStream out = null;
+ private boolean started = false;
+ private boolean closeStream = false;
+ private boolean headerWritten = false;
+ private boolean repeatInfinite = false;
+ private int delay = 100;
+ private int frameCount = 1;
+ private int seqNum = 0;
+ private boolean processedFirstFrame = false;
+
+ public void setDelay(int ms) {
+ delay = ms;
+ }
+
+ public void setRepeat(boolean infinite) {
+ repeatInfinite = infinite;
+ }
+
+ public void setNumFrames(int frames) {
+ frameCount = frames;
+ }
+
+ public boolean start(OutputStream os) {
+ if (os == null)
+ return false;
+ boolean ok = true;
+ closeStream = false;
+ out = new DataOutputStream(os);
+ try {
+ out.write(HEADER);
+ } catch (IOException e) {
+ ok = false;
+ }
+ return started = ok;
+ }
+
+ public boolean start(String file) {
+ boolean ok = true;
+ try {
+ out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
+ ok = start(out);
+ closeStream = true;
+ } catch (IOException e) {
+ ok = false;
+ }
+ return started = ok;
+ }
+
+ public boolean finish() {
+ if (!started)
+ return false;
+ try {
+ if (closeStream)
+ out.close();
+ } catch (IOException ioe) {
+ }
+
+ return true;
+ }
+
+ public boolean addFrame(BufferedImage im) {
+ if ((im == null) || !started)
+ return false;
+ try {
+ PngStream pStrm;
+ boolean sawIDAT = false;
+ {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(im.getHeight() * im.getWidth()); //a conservative estimate
+ ImageIO.write(im, ExportType.Pngs.getExtension(), baos);
+ pStrm = new PngStream(baos.toByteArray());
+ baos.reset();
+ }
+ Chunk chunk = pStrm.readNextChunk(pStrm);
+ while (chunk != null) {
+ switch (chunk.type) {
+ case IHDR:
+ if (!headerWritten) {
+ chunk.write(out);
+ headerWritten = true;
+ Chunk acTLChunk = new Chunk(8, acTL);
+
+ acTLChunk.injectInt(0, frameCount);
+ acTLChunk.injectInt(4, repeatInfinite ? 0 : 1);
+ acTLChunk.calcCRC();
+ acTLChunk.write(out);
+ }
+ break;
+
+ case IDAT:
+ if (!sawIDAT) {
+ Chunk fcTLChunk = new Chunk(26, fcTL);
+ fcTLChunk.injectInt(0, seqNum);
+ fcTLChunk.injectInt(4, im.getWidth());
+ fcTLChunk.injectInt(8, im.getHeight());
+ fcTLChunk.injectInt(12, 0);
+ fcTLChunk.injectInt(16, 0);
+ fcTLChunk.injectShort(20, delay);
+ fcTLChunk.injectShort(22, 1000);
+ fcTLChunk.injectShort(24, 0);
+ fcTLChunk.calcCRC();
+ fcTLChunk.write(out);
+ sawIDAT = true;
+ }
+ if (!processedFirstFrame) {
+ chunk.write(out);
+ } else {
+ Chunk fdATChunk = new Chunk(chunk.length + 4, fdAT);
+ fdATChunk.injectInt(0, seqNum);
+ System.arraycopy(chunk.data, 0, fdATChunk.data, 4, chunk.length);
+ fdATChunk.calcCRC();
+ fdATChunk.write(out);
+ }
+ break;
+
+ case IEND:
+ if (seqNum >= frameCount)
+ chunk.write(out);
+ break;
+ }
+ chunk = pStrm.readNextChunk(pStrm);
+ }
+ processedFirstFrame = true;
+ seqNum++;
+
+ return true;
+ } catch (IOException ioe) {
+ return false;
+ } finally {
+
+ }
+ }
+
+ static class PngStream {
+ public PngStream(byte[] pngData) {
+ pos = HEADER.length;
+ data = pngData;
+ }
+
+ public Chunk readNextChunk(PngStream pStrm) {
+ if (pos >= data.length)
+ return null;
+
+ Chunk c = new Chunk(readNextInt(), readNextInt());
+
+ System.arraycopy(data, pos, c.data, 0, c.length);
+ pos += c.length;
+ c.crc = readNextInt();
+ return c;
+ }
+
+ private int readNextInt() {
+ int val = 0;
+ for (int i = 0; i < 4; i++) {
+ val <<= 8;
+ val |= (0x00FF & data[pos++]);
+ }
+ return val;
+ }
+
+ public int pos;
+ public byte[] data;
+ }
+
+ static class Chunk {
+ private static long crcTable[] = new long[256];
+ static {
+ long c;
+ int n, k;
+
+ for (n = 0; n < 256; n++) {
+ c = (long) n;
+ for (k = 0; k < 8; k++) {
+ if ((c & 1) != 0)
+ c = 0xedb88320L ^ (c >> 1);
+ else
+ c = c >> 1;
+ }
+ crcTable[n] = c;
+ }
+ }
+
+ public int length;
+ public int type;
+ public byte[] data;
+ public int crc;
+
+ public Chunk(int len, int chunkType) {
+ length = len;
+ type = chunkType;
+ data = new byte[length];
+ crc = 0;
+ }
+
+ public void write(DataOutputStream out) throws IOException {
+ out.writeInt(length);
+ out.writeInt(type);
+ out.write(data);
+ out.writeInt(crc);
+ }
+
+ public void injectInt(int offset, int value) {
+ data[offset++] = (byte)(value >> 24 & 0x00FF);
+ data[offset++] = (byte)(value >> 16 & 0x00FF);
+ data[offset++] = (byte)(value >> 8 & 0x00FF);
+ data[offset] = (byte)(value & 0x00FF);
+ }
+
+ public void injectShort(int offset, int value) {
+ data[offset++] = (byte)(value >> 8 & 0x00FF);
+ data[offset] = (byte)(value & 0x00FF);
+ }
+
+ public void calcCRC() {
+ long c = -1;
+ for (int n = 0; n < length; n++) {
+ c = crcTable[(int)((c ^ data[n]) & 0x00FF)] ^ (c >> 8);
+ }
+
+ c &= -1;
+ crc = (int)c;
+ }
+ }
+}
Property changes on: trunk/patchanim/src/com/mebigfatguy/apng/APngEncoder.java
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Name: svn:eol-style
+ native
Modified: trunk/patchanim/src/com/mebigfatguy/patchanim/ExportType.java
===================================================================
--- trunk/patchanim/src/com/mebigfatguy/patchanim/ExportType.java 2008-02-10 01:12:47 UTC (rev 129)
+++ trunk/patchanim/src/com/mebigfatguy/patchanim/ExportType.java 2008-02-10 04:33:41 UTC (rev 130)
@@ -9,6 +9,7 @@
* <li><b>Pngs</b> a series of Png files in a directory</li>
* <li><b>Gifs</b> a series of Gif files in a directory</li>
* <li><b>AnimatedGif</b> an animated gif file</li>
+ * <li><b>AnimatedPng</b> an animated png file</li>
* <li><b>Mpeg</b> an animated mpeg file</li>
* </ul>
*/
@@ -17,6 +18,7 @@
Pngs("png", true, PatchAnimBundle.PNGSERIESFILTER),
Gifs("gif", true, PatchAnimBundle.GIFSERIESFILTER),
AnimatedGif("gif", false, PatchAnimBundle.ANIMATEDGIFFILTER),
+ AnimatedPng("png", false, PatchAnimBundle.ANIMATEDPNGFILTER),
MPeg("mpeg", false, PatchAnimBundle.MPEGFILTER);
private String ext;
Modified: trunk/patchanim/src/com/mebigfatguy/patchanim/gui/JPatchAnimFrame.java
===================================================================
--- trunk/patchanim/src/com/mebigfatguy/patchanim/gui/JPatchAnimFrame.java 2008-02-10 01:12:47 UTC (rev 129)
+++ trunk/patchanim/src/com/mebigfatguy/patchanim/gui/JPatchAnimFrame.java 2008-02-10 04:33:41 UTC (rev 130)
@@ -60,6 +60,7 @@
private JMenuItem exportPngsItem;
private JMenuItem exportGifsItem;
private JMenuItem exportAnimatedGifItem;
+ private JMenuItem exportAnimatedPngItem;
private JMenuItem quitItem;
private PatchAnimDocument document;
private File documentLocation;
@@ -106,11 +107,13 @@
exportPngsItem = new JMenuItem(rb.getString(PatchAnimBundle.PNGSERIES));
exportGifsItem = new JMenuItem(rb.getString(PatchAnimBundle.GIFSERIES));
exportAnimatedGifItem = new JMenuItem(rb.getString(PatchAnimBundle.ANIMATEDGIF));
+ exportAnimatedPngItem = new JMenuItem(rb.getString(PatchAnimBundle.ANIMATEDPNG));
exportMenu.add(exportJpgsItem);
exportMenu.add(exportPngsItem);
exportMenu.add(exportGifsItem);
exportMenu.addSeparator();
exportMenu.add(exportAnimatedGifItem);
+ exportMenu.add(exportAnimatedPngItem);
fileMenu.add(exportMenu);
fileMenu.addSeparator();
quitItem = new JMenuItem(rb.getString(PatchAnimBundle.QUIT));
@@ -236,6 +239,12 @@
}
});
+ exportAnimatedPngItem.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent ae) {
+ export(ExportType.AnimatedPng);
+ }
+ });
+
quitItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
try {
Modified: trunk/patchanim/src/com/mebigfatguy/patchanim/io/PatchExporter.java
===================================================================
--- trunk/patchanim/src/com/mebigfatguy/patchanim/io/PatchExporter.java 2008-02-10 01:12:47 UTC (rev 129)
+++ trunk/patchanim/src/com/mebigfatguy/patchanim/io/PatchExporter.java 2008-02-10 04:33:41 UTC (rev 130)
@@ -29,6 +29,7 @@
import javax.imageio.ImageIO;
import com.fmsware.gif.AnimatedGifEncoder;
+import com.mebigfatguy.apng.APngEncoder;
import com.mebigfatguy.patchanim.AnimationType;
import com.mebigfatguy.patchanim.ExportType;
import com.mebigfatguy.patchanim.OutOfBoundsColor;
@@ -42,6 +43,7 @@
private ExportType type;
private File loc;
private AnimatedGifEncoder agEncoder;
+ private APngEncoder apngEncoder;
private int totalImages;
private Set<ExportListener> elisteners = new HashSet<ExportListener>();
@@ -50,6 +52,8 @@
loc = location;
if (type == ExportType.AnimatedGif)
agEncoder = new AnimatedGifEncoder();
+ else if (type == ExportType.AnimatedPng)
+ apngEncoder = new APngEncoder();
else
agEncoder = null;
}
@@ -70,6 +74,8 @@
baseName = baseName + dotExt;
if (type == ExportType.AnimatedGif)
agEncoder.start(new File(loc, baseName).getPath());
+ else if (type == ExportType.AnimatedPng)
+ apngEncoder.start(new File(loc, baseName).getPath());
}
totalImages = calcImageCount(document);
@@ -84,6 +90,9 @@
if (type == ExportType.AnimatedGif) {
agEncoder.setRepeat((atype != AnimationType.None) ? 0 : -1);
+ } else if (type == ExportType.AnimatedPng) {
+ apngEncoder.setRepeat(atype != AnimationType.None);
+ apngEncoder.setNumFrames(totalImages);
}
if (lastPatch == 0) {
@@ -118,6 +127,8 @@
} finally {
if (type == ExportType.AnimatedGif) {
agEncoder.finish();
+ } else if (type == ExportType.AnimatedPng) {
+ apngEncoder.finish();
}
}
}
@@ -137,6 +148,9 @@
} else if (type == ExportType.AnimatedGif) {
agEncoder.addFrame(image);
agEncoder.setDelay(100);
+ } else if (type == ExportType.AnimatedPng) {
+ apngEncoder.setDelay(100);
+ apngEncoder.addFrame(image);
}
else
ImageIO.write(image, type.getExtension(), imageFile);
Modified: trunk/patchanim/src/com/mebigfatguy/patchanim/main/PatchAnimBundle.java
===================================================================
--- trunk/patchanim/src/com/mebigfatguy/patchanim/main/PatchAnimBundle.java 2008-02-10 01:12:47 UTC (rev 129)
+++ trunk/patchanim/src/com/mebigfatguy/patchanim/main/PatchAnimBundle.java 2008-02-10 04:33:41 UTC (rev 130)
@@ -38,6 +38,8 @@
public static final String GIFSERIESFILTER = "patchanim.filter.gifs";
public static final String ANIMATEDGIF = "patchanim.animatedgif";
public static final String ANIMATEDGIFFILTER = "patchanim.filter.animatedgif";
+ public static final String ANIMATEDPNG = "patchanim.apng";
+ public static final String ANIMATEDPNGFILTER = "patchanim.filter.apng";
public static final String MPEG = "patchanim.mpeg";
public static final String MPEGFILTER = "patchanim.filter.mpeg";
public static final String EXPORTINGFILE = "patchanim.exportfile";
Modified: trunk/patchanim/src/com/mebigfatguy/patchanim/resources.properties
===================================================================
--- trunk/patchanim/src/com/mebigfatguy/patchanim/resources.properties 2008-02-10 01:12:47 UTC (rev 129)
+++ trunk/patchanim/src/com/mebigfatguy/patchanim/resources.properties 2008-02-10 04:33:41 UTC (rev 130)
@@ -31,6 +31,8 @@
patchanim.filter.gifs = Gif Files (*.gif)
patchanim.animatedgif = an Animated Gif
patchanim.filter.animatedgif = Gif Files (*.gif)
+patchanim.apng = an Animated Png
+patchanim.filter.apng = (Png Files (*.png)
patchanim.mpeg = an MPEG
patchanim.filter.mpeg = MPEG Files (*.mpg)
patchanim.exportfile = Exporting Animation
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|