[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. |