From 598b9919c00aa0d59d704e3b219eb73d33bd0e79 Mon Sep 17 00:00:00 2001 From: Jordan Orelli Date: Sat, 3 Apr 2021 10:48:13 -0500 Subject: [PATCH] add processing 3.0 compatible gifAnimation library --- .../examples/gifDisplay/data/lavalamp.gif | Bin 0 -> 26636 bytes .../examples/gifDisplay/gifDisplay.pde | 55 + .../examples/gifExport/data/processing.png | Bin 0 -> 23902 bytes .../examples/gifExport/gifExport.pde | 41 + libraries/gifAnimation/library.properties | 57 + .../gifAnimation/library/gifAnimation.jar | Bin 0 -> 19359 bytes libraries/gifAnimation/src/Gif.java | 322 ++++ libraries/gifAnimation/src/GifDecoder.java | 802 ++++++++++ libraries/gifAnimation/src/GifEncoder.java | 1292 +++++++++++++++++ libraries/gifAnimation/src/GifMaker.java | 168 +++ 10 files changed, 2737 insertions(+) create mode 100644 libraries/gifAnimation/examples/gifDisplay/data/lavalamp.gif create mode 100644 libraries/gifAnimation/examples/gifDisplay/gifDisplay.pde create mode 100644 libraries/gifAnimation/examples/gifExport/data/processing.png create mode 100644 libraries/gifAnimation/examples/gifExport/gifExport.pde create mode 100644 libraries/gifAnimation/library.properties create mode 100644 libraries/gifAnimation/library/gifAnimation.jar create mode 100644 libraries/gifAnimation/src/Gif.java create mode 100644 libraries/gifAnimation/src/GifDecoder.java create mode 100644 libraries/gifAnimation/src/GifEncoder.java create mode 100755 libraries/gifAnimation/src/GifMaker.java diff --git a/libraries/gifAnimation/examples/gifDisplay/data/lavalamp.gif b/libraries/gifAnimation/examples/gifDisplay/data/lavalamp.gif new file mode 100644 index 0000000000000000000000000000000000000000..7c2b2248c0e2c413a8424a7515191f407c5618be GIT binary patch literal 26636 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 literal 0 HcmV?d00001 diff --git a/libraries/gifAnimation/examples/gifExport/gifExport.pde b/libraries/gifAnimation/examples/gifExport/gifExport.pde new file mode 100644 index 0000000..d03e11a --- /dev/null +++ b/libraries/gifAnimation/examples/gifExport/gifExport.pde @@ -0,0 +1,41 @@ +/* +* Demonstrates the use of the GifAnimation library. + * Exports a GIF-File to the sketch folder if space + * bar is pressed. Wow, feels like 90's! ;) + */ + +import gifAnimation.*; +import processing.opengl.*; + +GifMaker gifExport; +PImage logo; +float rotation = 0.0; + +public void setup() { + size(200, 200, OPENGL); + frameRate(12); + logo = loadImage("processing.png"); + + println("gifAnimation " + Gif.version()); + gifExport = new GifMaker(this, "export.gif"); + gifExport.setRepeat(0); // make it an "endless" animation + gifExport.setTransparent(0,0,0); // make black the transparent color. every black pixel in the animation will be transparent + // GIF doesn't know have alpha values like processing. a pixel can only be totally transparent or totally opaque. + // set the processing background and the transparent gif color to the same value as the gifs destination background color + // (e.g. the website bg-color). Like this you can have the antialiasing from processing in the gif. +} + +void draw() { + background(0); + translate(width/2, height/2); + rotation+=.1; + rotateY(rotation); + image(logo, -logo.width/2,-logo.height/2); + gifExport.setDelay(1); + gifExport.addFrame(); +} + +void keyPressed() { + gifExport.finish(); + println("gif saved"); +} diff --git a/libraries/gifAnimation/library.properties b/libraries/gifAnimation/library.properties new file mode 100644 index 0000000..34e7178 --- /dev/null +++ b/libraries/gifAnimation/library.properties @@ -0,0 +1,57 @@ +# More on this file here: https://github.com/processing/processing/wiki/Library-Basics +# UTF-8 supported. + +# The name of your Library as you want it formatted. +name = GifAnimation + +# List of authors. Links can be provided using the syntax [author name](url). +authors = [Patrick Meister, Jerome Saint-Clair](http://www.extrapixel.ch) + +# A web page for your Library, NOT a direct link to where to download it. +url = http://github.com/01010101/GifAnimation + +# The category (or categories) of your Library, must be from the following list: +# "3D" "Animation" "Compilations" "Data" +# "Fabrication" "Geometry" "GUI" "Hardware" +# "I/O" "Language" "Math" "Simulation" +# "Sound" "Utilities" "Typography" "Video & Vision" +# +# If a value other than those listed is used, your Library will listed as +# "Other". Many categories must be comma-separated. +categories = Animation + +# A short sentence (or fragment) to summarize the Library's function. This will +# be shown from inside the PDE when the Library is being installed. Avoid +# repeating the name of your Library here. Also, avoid saying anything redundant +# like mentioning that it's a Library. This should start with a capitalized +# letter, and end with a period. +sentence = A library to play and export GIF animations. + +# Additional information suitable for the Processing website. The value of +# 'sentence' always will be prepended, so you should start by writing the +# second sentence here. If your Library only works on certain operating systems, +# mention it here. +paragraph = Processing v3.x port by [Jerome Saint-Clair (01010101)](http://saint-clair.net/). GIFEncoder & GIFDecoder classes by Kevin Weiner. + +# Links in the 'sentence' and 'paragraph' attributes can be inserted using the +# same syntax as for authors. +# That is, [here is a link to Processing](http://processing.org/) + +# A version number that increments once with each release. This is used to +# compare different versions of the same Library, and check if an update is +# available. You should think of it as a counter, counting the total number of +# releases you've had. +version = 3 # This must be parsable as an int + +# The version as the user will see it. If blank, the version attribute will be +# used here. This should be a single word, with no spaces. +prettyVersion = 3.0.0 # This is treated as a String + +# The min and max revision of Processing compatible with your Library. +# Note that these fields use the revision and not the version of Processing, +# parsable as an int. For example, the revision number for 2.2.1 is 227. +# You can find the revision numbers in the change log: https://raw.githubusercontent.com/processing/processing/master/build/shared/revisions.txt +# Only use maxRevision (or minRevision), when your Library is known to +# break in a later (or earlier) release. Otherwise, use the default value 0. +minRevision = 3.0 +maxRevision = 0 diff --git a/libraries/gifAnimation/library/gifAnimation.jar b/libraries/gifAnimation/library/gifAnimation.jar new file mode 100644 index 0000000000000000000000000000000000000000..4c22cae7341c3d3d7412141faa0e90c3e385b224 GIT binary patch literal 19359 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 literal 0 HcmV?d00001 diff --git a/libraries/gifAnimation/src/Gif.java b/libraries/gifAnimation/src/Gif.java new file mode 100644 index 0000000..41c7fcd --- /dev/null +++ b/libraries/gifAnimation/src/Gif.java @@ -0,0 +1,322 @@ +/* + * GifAnimation is a processing library to play gif animations and to + * extract frames from a gif file. It can also export animated GIF animations + * This file class is under a GPL license. The Decoder used to open the + * gif files was written by Kevin Weiner. please see the separate copyright + * notice in the header of the GifDecoder / GifEncoder class. + * + * by extrapixel 2007 + * http://extrapixel.ch + * + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +package gifAnimation; + +import java.awt.image.BufferedImage; +import java.io.*; + +import processing.core.*; + +public class Gif extends PImage implements PConstants, Runnable { + private PApplet parent; + Thread runner; + // if the animation is currently playing + private boolean play; + // if the animation is currently looping + private boolean loop; + // wether the repeat setting from the gif-file should be ignored + private boolean ignoreRepeatSetting = false; + // nr of repeats specified in the gif-file. 0 means repeat forever + private int repeatSetting = 1; + // how often this animation has repeated since last call to play() + private int repeatCount = 0; + // the current frame number + private int currentFrame; + // array containing the frames as PImages + private PImage[] frames; + // array containing the delay in ms of every frame + private int[] delays; + // last time the frame changed + private int lastJumpTime; + // version + private static String version = "3.1"; + + public Gif(PApplet parent, String filename) { + // this creates a fake image so that the first time this + // attempts to draw, something happens that's not an exception + super(1, 1, ARGB); + + this.parent = parent; + + // create the GifDecoder + GifDecoder gifDecoder = createDecoder(parent, filename); + + // fill up the PImage and the delay arrays + frames = extractFrames(gifDecoder); + delays = extractDelays(gifDecoder); + + // get the GIFs repeat count + repeatSetting = gifDecoder.getLoopCount(); + + // re-init our PImage with the new size + super.init(frames[0].width, frames[0].height, ARGB); + jump(0); + parent.registerMethod("dispose", this); + + // and now, make the magic happen + runner = new Thread(this); + runner.start(); + } + + public void dispose() { + // fin + // System.out.println("disposing"); + stop(); + runner = null; + } + + /* + * the thread's run method + */ + public void run() { + while (Thread.currentThread() == runner) { + try { + Thread.sleep(5); + } catch (InterruptedException e) { + } + + if (play) { + // if playing, check if we need to go to next frame + + if (parent.millis() - lastJumpTime >= delays[currentFrame]) { + // we need to jump + + if (currentFrame == frames.length - 1) { + // its the last frame + if (loop) { + jump(0); // loop is on, so rewind + } else if (!ignoreRepeatSetting) { + // we're not looping, but we need to respect the + // GIF's repeat setting + repeatCount++; + if (repeatSetting == 0) { + // we need to repeat forever + jump(0); + } else if (repeatCount == repeatSetting) { + // stop repeating, we've reached the repeat + // setting + stop(); + } + } else { + // no loop & ignoring the repeat setting, so just + // stop. + stop(); + } + } else { + // go to the next frame + jump(currentFrame + 1); + } + } + } + } + } + + /* + * creates an input stream using processings createInput() method to read + * from the sketch data-directory + */ + private static InputStream createInputStream(PApplet parent, String filename) { + InputStream inputStream = parent.createInput(filename); + return inputStream; + } + + /* + * in case someone wants to mess with the frames directly, they can get an + * array of PImages containing the animation frames. without having a + * gif-object with a seperate thread + * + * it takes a filename of a file in the datafolder. + */ + public static PImage[] getPImages(PApplet parent, String filename) { + GifDecoder gifDecoder = createDecoder(parent, filename); + return extractFrames(gifDecoder); + } + + /* + * probably someone wants all the frames even if he has a playback-gif... + */ + public PImage[] getPImages() { + return frames; + } + + /* + * creates a GifDecoder object and loads a gif file + */ + private static GifDecoder createDecoder(PApplet parent, String filename) { + GifDecoder gifDecoder = new GifDecoder(); + gifDecoder.read(createInputStream(parent, filename)); + return gifDecoder; + } + + /* + * creates a PImage-array of gif frames in a GifDecoder object + */ + private static PImage[] extractFrames(GifDecoder gifDecoder) { + int n = gifDecoder.getFrameCount(); + + PImage[] frames = new PImage[n]; + + for (int i = 0; i < n; i++) { + BufferedImage frame = gifDecoder.getFrame(i); + frames[i] = new PImage(frame.getWidth(), frame.getHeight(), ARGB); + System.arraycopy(frame.getRGB(0, 0, frame.getWidth(), frame + .getHeight(), null, 0, frame.getWidth()), 0, + frames[i].pixels, 0, frame.getWidth() * frame.getHeight()); + } + return frames; + } + + /* + * creates an int-array of frame delays in the gifDecoder object + */ + private static int[] extractDelays(GifDecoder gifDecoder) { + int n = gifDecoder.getFrameCount(); + int[] delays = new int[n]; + for (int i = 0; i < n; i++) { + delays[i] = gifDecoder.getDelay(i); // display duration of frame in + // milliseconds + } + return delays; + } + + /* + * Can be called to ignore the repeat-count set in the gif-file. this does + * not affect loop()/noLoop() settings. + */ + public void ignoreRepeat() { + ignoreRepeatSetting = true; + } + + /* + * returns the number of repeats that is specified in the gif-file 0 means + * repeat forever + */ + public int getRepeat() { + return repeatSetting; + } + + /* + * returns true if this GIF object is playing + */ + public boolean isPlaying() { + return play; + } + + /* + * returns the current frame number + */ + public int currentFrame() { + return currentFrame; + } + + /* + * returns true if the animation is set to loop + */ + public boolean isLooping() { + return loop; + } + + /* + * returns true if this animation is set to ignore the file's repeat setting + */ + public boolean isIgnoringRepeat() { + return ignoreRepeatSetting; + } + + /* + * returns the version of the library + */ + public static String version() { + return version; + } + + /* + * following methods mimic the behaviour of processing's movie class. + */ + + /** + * Begin playing the animation, with no repeat. + */ + public void play() { + play = true; + if (!ignoreRepeatSetting) { + repeatCount = 0; + } + } + + /** + * Begin playing the animation, with repeat. + */ + public void loop() { + play = true; + loop = true; + } + + /** + * Shut off the repeating loop. + */ + public void noLoop() { + loop = false; + } + + /** + * Pause the animation at its current frame. + */ + public void pause() { + // System.out.println("pause"); + play = false; + } + + /** + * Stop the animation, and rewind. + */ + public void stop() { + //System.out.println("stop"); + play = false; + currentFrame = 0; + repeatCount = 0; + } + + /** + * Jump to a specific location (in frames). if the frame does not exist, go + * to last frame + * @param where : file location (in sketch) + */ + public void jump(int where) { + if (frames.length > where) { + currentFrame = where; + + // update the pixel-array + loadPixels(); + System.arraycopy(frames[currentFrame].pixels, 0, pixels, 0, width + * height); + updatePixels(); + + // set the jump time + lastJumpTime = parent.millis(); + } + } + +} diff --git a/libraries/gifAnimation/src/GifDecoder.java b/libraries/gifAnimation/src/GifDecoder.java new file mode 100644 index 0000000..5dbab33 --- /dev/null +++ b/libraries/gifAnimation/src/GifDecoder.java @@ -0,0 +1,802 @@ +package gifAnimation; + +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; +import java.io.BufferedInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; + +/** + * Class GifDecoder - Decodes a GIF file into one or more frames.
+ * + *
+ *  Example:
+ *     GifDecoder d = new GifDecoder();
+ *     d.read("sample.gif");
+ *     int n = d.getFrameCount();
+ *     for (int i = 0; i < n; i++) {
+ *        BufferedImage frame = d.getFrame(i);  // frame i
+ *        int t = d.getDelay(i);  // display duration of frame in milliseconds
+ *        // do something with frame
+ *     }
+ * 
+ * + * No copyright asserted on the source code of this class. May be used for any + * purpose, however, refer to the Unisys LZW patent for any additional + * restrictions. Please forward any corrections to kweiner@fmsware.com. + * + * @author Kevin Weiner, FM Software; LZW decoder adapted from John Cristy's + * ImageMagick. + * @version 1.03 November 2003 + * + */ + +public class GifDecoder { + + /** + * File read status: No errors. + */ + public static final int STATUS_OK = 0; + + /** + * File read status: Error decoding file (may be partially decoded) + */ + public static final int STATUS_FORMAT_ERROR = 1; + + /** + * File read status: Unable to open source. + */ + public static final int STATUS_OPEN_ERROR = 2; + + protected BufferedInputStream in; + + protected int status; + + protected int width; // full image width + + protected int height; // full image height + + protected boolean gctFlag; // global color table used + + protected int gctSize; // size of global color table + + protected int loopCount = 1; // iterations; 0 = repeat forever + + protected int[] gct; // global color table + + protected int[] lct; // local color table + + protected int[] act; // active color table + + protected int bgIndex; // background color index + + protected int bgColor; // background color + + protected int lastBgColor; // previous bg color + + protected int pixelAspect; // pixel aspect ratio + + protected boolean lctFlag; // local color table flag + + protected boolean interlace; // interlace flag + + protected int lctSize; // local color table size + + protected int ix, iy, iw, ih; // current image rectangle + + protected Rectangle lastRect; // last image rect + + protected BufferedImage image; // current frame + + protected BufferedImage lastImage; // previous frame + + protected byte[] block = new byte[256]; // current data block + + protected int blockSize = 0; // block size + + // last graphic control extension info + protected int dispose = 0; + + // 0=no action; 1=leave in place; 2=restore to bg; 3=restore to prev + protected int lastDispose = 0; + + protected boolean transparency = false; // use transparent color + + protected int delay = 0; // delay in milliseconds + + protected int transIndex; // transparent color index + + protected static final int MaxStackSize = 4096; + + // max decoder pixel stack size + + // LZW decoder working arrays + protected short[] prefix; + + protected byte[] suffix; + + protected byte[] pixelStack; + + protected byte[] pixels; + + protected ArrayList frames; // frames read from current file + + protected int frameCount; + + static class GifFrame { + public GifFrame(BufferedImage im, int del) { + image = im; + delay = del; + } + + public BufferedImage image; + + public int delay; + } + + /** + * Gets display duration for specified frame. + * + * @param n + * int index of frame + * @return delay in milliseconds + */ + public int getDelay(int n) { + // + delay = -1; + if ((n >= 0) && (n < frameCount)) { + delay = frames.get(n).delay; + } + return delay; + } + + /** + * Gets the number of frames read from file. + * + * @return frame count + */ + public int getFrameCount() { + return frameCount; + } + + /** + * Gets the first (or only) image read. + * + * @return BufferedImage containing first frame, or null if none. + */ + public BufferedImage getImage() { + return getFrame(0); + } + + /** + * Gets the "Netscape" iteration count, if any. A count of 0 means repeat + * indefinitiely. + * + * @return iteration count if one was specified, else 1. + */ + public int getLoopCount() { + return loopCount; + } + + /** + * Creates new frame image from current data (and previous frames as specified + * by their disposition codes). + */ + protected void setPixels() { + // expose destination image's pixels as int array + int[] dest = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); + + // fill in starting image contents based on last image's dispose code + if (lastDispose > 0) { + if (lastDispose == 3) { + // use image before last + int n = frameCount - 2; + if (n > 0) { + lastImage = getFrame(n - 1); + } else { + lastImage = null; + } + } + + if (lastImage != null) { + int[] prev = ((DataBufferInt) lastImage.getRaster().getDataBuffer()).getData(); + System.arraycopy(prev, 0, dest, 0, width * height); + // copy pixels + + if (lastDispose == 2) { + // fill last image rect area with background color + Graphics2D g = image.createGraphics(); + Color c = null; + if (transparency) { + c = new Color(0, 0, 0, 0); // assume background is transparent + } else { + c = new Color(lastBgColor); // use given background color + } + g.setColor(c); + g.setComposite(AlphaComposite.Src); // replace area + g.fill(lastRect); + g.dispose(); + } + } + } + + // copy each source line to the appropriate place in the destination + int pass = 1; + int inc = 8; + int iline = 0; + for (int i = 0; i < ih; i++) { + int line = i; + if (interlace) { + if (iline >= ih) { + pass++; + switch (pass) { + case 2: + iline = 4; + break; + case 3: + iline = 2; + inc = 4; + break; + case 4: + iline = 1; + inc = 2; + } + } + line = iline; + iline += inc; + } + line += iy; + if (line < height) { + int k = line * width; + int dx = k + ix; // start of line in dest + int dlim = dx + iw; // end of dest line + if ((k + width) < dlim) { + dlim = k + width; // past dest edge + } + int sx = i * iw; // start of line in source + while (dx < dlim) { + // map color and insert in destination + int index = ((int) pixels[sx++]) & 0xff; + int c = act[index]; + if (c != 0) { + dest[dx] = c; + } + dx++; + } + } + } + } + + /** + * Gets the image contents of frame n. + * @param n : frame number + * @return BufferedImage representation of frame, or null if n is invalid. + */ + public BufferedImage getFrame(int n) { + BufferedImage im = null; + if ((n >= 0) && (n < frameCount)) { + im = frames.get(n).image; + } + return im; + } + + /** + * Gets image size. + * + * @return GIF image dimensions + */ + public Dimension getFrameSize() { + return new Dimension(width, height); + } + + /** + * Reads GIF image from stream + * + * @param is : BufferedInputStream containing GIF file. + * + * @return read status code (0 = no errors) + */ + public int read(BufferedInputStream is) { + init(); + if (is != null) { + in = is; + readHeader(); + if (!err()) { + readContents(); + if (frameCount < 0) { + status = STATUS_FORMAT_ERROR; + } + } + } else { + status = STATUS_OPEN_ERROR; + } + try { + is.close(); + } catch (IOException e) { + } + return status; + } + + /** + * Reads GIF image from stream + * + * @param is : InputStream containing GIF file. + * + * @return read status code (0 = no errors) + */ + public int read(InputStream is) { + init(); + if (is != null) { + if (!(is instanceof BufferedInputStream)) + is = new BufferedInputStream(is); + in = (BufferedInputStream) is; + readHeader(); + if (!err()) { + readContents(); + if (frameCount < 0) { + status = STATUS_FORMAT_ERROR; + } + } + } else { + status = STATUS_OPEN_ERROR; + } + try { + is.close(); + } catch (IOException e) { + } + return status; + } + + /** + * Reads GIF file from specified file/URL source (URL assumed if name contains + * ":/" or "file:") + * + * @param name : String containing source + * @return read status code (0 = no errors) + */ + public int read(String name) { + status = STATUS_OK; + try { + name = name.trim().toLowerCase(); + if ((name.indexOf("file:") >= 0) || (name.indexOf(":/") > 0)) { + URL url = new URL(name); + in = new BufferedInputStream(url.openStream()); + } else { + in = new BufferedInputStream(new FileInputStream(name)); + } + status = read(in); + } catch (IOException e) { + status = STATUS_OPEN_ERROR; + } + + return status; + } + + /** + * Decodes LZW image data into pixel array. Adapted from John Cristy's + * ImageMagick. + */ + protected void decodeImageData() { + int NullCode = -1; + int npix = iw * ih; + int available, clear, code_mask, code_size, end_of_information, in_code, old_code, bits, code, count, i, datum, data_size, first, top, bi, pi; + + if ((pixels == null) || (pixels.length < npix)) { + pixels = new byte[npix]; // allocate new pixel array + } + if (prefix == null) + prefix = new short[MaxStackSize]; + if (suffix == null) + suffix = new byte[MaxStackSize]; + if (pixelStack == null) + pixelStack = new byte[MaxStackSize + 1]; + + // Initialize GIF data stream decoder. + + data_size = read(); + clear = 1 << data_size; + end_of_information = clear + 1; + available = clear + 2; + old_code = NullCode; + code_size = data_size + 1; + code_mask = (1 << code_size) - 1; + for (code = 0; code < clear; code++) { + prefix[code] = 0; + suffix[code] = (byte) code; + } + + // Decode GIF pixel stream. + + datum = bits = count = first = top = pi = bi = 0; + + for (i = 0; i < npix;) { + if (top == 0) { + if (bits < code_size) { + // Load bytes until there are enough bits for a code. + if (count == 0) { + // Read a new data block. + count = readBlock(); + if (count <= 0) + break; + bi = 0; + } + datum += (((int) block[bi]) & 0xff) << bits; + bits += 8; + bi++; + count--; + continue; + } + + // Get the next code. + + code = datum & code_mask; + datum >>= code_size; + bits -= code_size; + + // Interpret the code + + if ((code > available) || (code == end_of_information)) + break; + if (code == clear) { + // Reset decoder. + code_size = data_size + 1; + code_mask = (1 << code_size) - 1; + available = clear + 2; + old_code = NullCode; + continue; + } + if (old_code == NullCode) { + pixelStack[top++] = suffix[code]; + old_code = code; + first = code; + continue; + } + in_code = code; + if (code == available) { + pixelStack[top++] = (byte) first; + code = old_code; + } + while (code > clear) { + pixelStack[top++] = suffix[code]; + code = prefix[code]; + } + first = ((int) suffix[code]) & 0xff; + + // Add a new string to the string table, + + if (available >= MaxStackSize) + break; + pixelStack[top++] = (byte) first; + prefix[available] = (short) old_code; + suffix[available] = (byte) first; + available++; + if (((available & code_mask) == 0) && (available < MaxStackSize)) { + code_size++; + code_mask += available; + } + old_code = in_code; + } + + // Pop a pixel off the pixel stack. + + top--; + pixels[pi++] = pixelStack[top]; + i++; + } + + for (i = pi; i < npix; i++) { + pixels[i] = 0; // clear missing pixels + } + + } + + /** + * Returns true if an error was encountered during reading/decoding + */ + protected boolean err() { + return status != STATUS_OK; + } + + /** + * Initializes or re-initializes reader + */ + protected void init() { + status = STATUS_OK; + frameCount = 0; + frames = new ArrayList(); + gct = null; + lct = null; + } + + /** + * Reads a single byte from the input stream. + */ + protected int read() { + int curByte = 0; + try { + curByte = in.read(); + } catch (IOException e) { + status = STATUS_FORMAT_ERROR; + } + return curByte; + } + + /** + * Reads next variable length block from input. + * + * @return number of bytes stored in "buffer" + */ + protected int readBlock() { + blockSize = read(); + int n = 0; + if (blockSize > 0) { + try { + int count = 0; + while (n < blockSize) { + count = in.read(block, n, blockSize - n); + if (count == -1) + break; + n += count; + } + } catch (IOException e) { + } + + if (n < blockSize) { + status = STATUS_FORMAT_ERROR; + } + } + return n; + } + + /** + * Reads color table as 256 RGB integer values + * + * @param ncolors + * int number of colors to read + * @return int array containing 256 colors (packed ARGB with full alpha) + */ + protected int[] readColorTable(int ncolors) { + int nbytes = 3 * ncolors; + int[] tab = null; + byte[] c = new byte[nbytes]; + int n = 0; + try { + n = in.read(c); + } catch (IOException e) { + } + if (n < nbytes) { + status = STATUS_FORMAT_ERROR; + } else { + tab = new int[256]; // max size to avoid bounds checks + int i = 0; + int j = 0; + while (i < ncolors) { + int r = ((int) c[j++]) & 0xff; + int g = ((int) c[j++]) & 0xff; + int b = ((int) c[j++]) & 0xff; + tab[i++] = 0xff000000 | (r << 16) | (g << 8) | b; + } + } + return tab; + } + + /** + * Main file parser. Reads GIF content blocks. + */ + protected void readContents() { + // read GIF file content blocks + boolean done = false; + while (!(done || err())) { + int code = read(); + switch (code) { + + case 0x2C: // image separator + readImage(); + break; + + case 0x21: // extension + code = read(); + switch (code) { + case 0xf9: // graphics control extension + readGraphicControlExt(); + break; + + case 0xff: // application extension + readBlock(); + String app = ""; + for (int i = 0; i < 11; i++) { + app += (char) block[i]; + } + if (app.equals("NETSCAPE2.0")) { + readNetscapeExt(); + } else + skip(); // don't care + break; + + default: // uninteresting extension + skip(); + } + break; + + case 0x3b: // terminator + done = true; + break; + + case 0x00: // bad byte, but keep going and see what happens + break; + + default: + status = STATUS_FORMAT_ERROR; + } + } + } + + /** + * Reads Graphics Control Extension values + */ + protected void readGraphicControlExt() { + read(); // block size + int packed = read(); // packed fields + dispose = (packed & 0x1c) >> 2; // disposal method + if (dispose == 0) { + dispose = 1; // elect to keep old image if discretionary + } + transparency = (packed & 1) != 0; + delay = readShort() * 10; // delay in milliseconds + transIndex = read(); // transparent color index + read(); // block terminator + } + + /** + * Reads GIF file header information. + */ + protected void readHeader() { + String id = ""; + for (int i = 0; i < 6; i++) { + id += (char) read(); + } + if (!id.startsWith("GIF")) { + status = STATUS_FORMAT_ERROR; + return; + } + + readLSD(); + if (gctFlag && !err()) { + gct = readColorTable(gctSize); + bgColor = gct[bgIndex]; + } + } + + /** + * Reads next frame image + */ + protected void readImage() { + ix = readShort(); // (sub)image position & size + iy = readShort(); + iw = readShort(); + ih = readShort(); + + int packed = read(); + lctFlag = (packed & 0x80) != 0; // 1 - local color table flag + interlace = (packed & 0x40) != 0; // 2 - interlace flag + // 3 - sort flag + // 4-5 - reserved + lctSize = 2 << (packed & 7); // 6-8 - local color table size + + if (lctFlag) { + lct = readColorTable(lctSize); // read table + act = lct; // make local table active + } else { + act = gct; // make global table active + if (bgIndex == transIndex) + bgColor = 0; + } + int save = 0; + if (transparency) { + save = act[transIndex]; + act[transIndex] = 0; // set transparent color if specified + } + + if (act == null) { + status = STATUS_FORMAT_ERROR; // no color table defined + } + + if (err()) + return; + + decodeImageData(); // decode pixel data + skip(); + + if (err()) + return; + + frameCount++; + + // create new image to receive frame data + image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE); + + setPixels(); // transfer pixel data to image + + frames.add(new GifFrame(image, delay)); // add image to frame list + + if (transparency) { + act[transIndex] = save; + } + resetFrame(); + + } + + /** + * Reads Logical Screen Descriptor + */ + protected void readLSD() { + + // logical screen size + width = readShort(); + height = readShort(); + + // packed fields + int packed = read(); + gctFlag = (packed & 0x80) != 0; // 1 : global color table flag + // 2-4 : color resolution + // 5 : gct sort flag + gctSize = 2 << (packed & 7); // 6-8 : gct size + + bgIndex = read(); // background color index + pixelAspect = read(); // pixel aspect ratio + } + + /** + * Reads Netscape extension to obtain iteration count + */ + protected void readNetscapeExt() { + do { + readBlock(); + if (block[0] == 1) { + // loop count sub-block + int b1 = ((int) block[1]) & 0xff; + int b2 = ((int) block[2]) & 0xff; + loopCount = (b2 << 8) | b1; + } + } while ((blockSize > 0) && !err()); + } + + /** + * Reads next 16-bit value, LSB first + */ + protected int readShort() { + // read 16-bit value, LSB first + return read() | (read() << 8); + } + + /** + * Resets frame state for reading next image. + */ + protected void resetFrame() { + lastDispose = dispose; + lastRect = new Rectangle(ix, iy, iw, ih); + lastImage = image; + lastBgColor = bgColor; +// int dispose = 0; +// boolean transparency = false; +// int delay = 0; + lct = null; + } + + /** + * Skips variable length blocks up to and including next zero length block. + */ + protected void skip() { + do { + readBlock(); + } while ((blockSize > 0) && !err()); + } +} diff --git a/libraries/gifAnimation/src/GifEncoder.java b/libraries/gifAnimation/src/GifEncoder.java new file mode 100644 index 0000000..bff95fd --- /dev/null +++ b/libraries/gifAnimation/src/GifEncoder.java @@ -0,0 +1,1292 @@ +package gifAnimation; + +import java.io.*; +import java.awt.*; +import java.awt.image.*; + +/** + * Class AnimatedGifEncoder - Encodes a GIF file consisting of one or more + * frames. + * + *
+ *  Example:
+ *     AnimatedGifEncoder e = new AnimatedGifEncoder();
+ *     e.start(outputFileName);
+ *     e.setDelay(1000);   // 1 frame per sec
+ *     e.addFrame(image1);
+ *     e.addFrame(image2);
+ *     e.finish();
+ * 
+ * + * No copyright asserted on the source code of this class. May be used for any + * purpose, however, refer to the Unisys LZW patent for restrictions on use of + * the associated LZWEncoder class. Please forward any corrections to + * kweiner@fmsware.com. + * + * @author Kevin Weiner, FM Software + * @version 1.03 November 2003 + * + */ + +public class GifEncoder { + + protected int width; // image size + + protected int height; + + protected Color transparent = null; // transparent color if given + + protected int transIndex; // transparent index in color table + + protected int repeat = -1; // no repeat + + protected int delay = 0; // frame delay (hundredths) + + protected boolean started = false; // ready to output frames + + protected OutputStream out; + + protected BufferedImage image; // current frame + + protected byte[] pixels; // BGR byte array from frame + + protected byte[] indexedPixels; // converted frame indexed to palette + + protected int colorDepth; // number of bit planes + + protected byte[] colorTab; // RGB palette + + protected boolean[] usedEntry = new boolean[256]; // active palette entries + + protected int palSize = 7; // color table size (bits-1) + + protected int dispose = -1; // disposal code (-1 = use default) + + protected boolean closeStream = false; // close stream when finished + + protected boolean firstFrame = true; + + protected boolean sizeSet = false; // if false, get size from first frame + + protected int sample = 10; // default sample interval for quantizer + + /** + * Sets the delay time between each frame, or changes it for subsequent frames + * (applies to last frame added). + * + * @param ms + * int delay time in milliseconds + */ + public void setDelay(int ms) { + delay = Math.round(ms / 10.0f); + } + + /** + * Sets the GIF frame disposal code for the last added frame and any + * subsequent frames. Default is 0 if no transparent color has been set, + * otherwise 2. + * + * @param code + * int disposal code. + */ + public void setDispose(int code) { + if (code >= 0) { + dispose = code; + } + } + + /** + * Sets the number of times the set of GIF frames should be played. Default is + * 1; 0 means play indefinitely. Must be invoked before the first image is + * added. + * + * @param iter + * int number of iterations. + * + */ + public void setRepeat(int iter) { + if (iter >= 0) { + repeat = iter; + } + } + + /** + * Sets the transparent color for the last added frame and any subsequent + * frames. Since all colors are subject to modification in the quantization + * process, the color in the final palette for each frame closest to the given + * color becomes the transparent color for that frame. May be set to null to + * indicate no transparent color. + * + * @param c + * Color to be treated as transparent on display. + */ + public void setTransparent(Color c) { + transparent = c; + } + + /** + * Adds next GIF frame. The frame is not written immediately, but is actually + * deferred until the next frame is received so that timing data can be + * inserted. Invoking finish() flushes all frames. If + * setSize was not invoked, the size of the first image is used + * for all subsequent frames. + * + * @param im + * BufferedImage containing frame to write. + * @return true if successful. + */ + public boolean addFrame(BufferedImage im) { + if ((im == null) || !started) { + return false; + } + boolean ok = true; + try { + if (!sizeSet) { + // use first frame's size + setSize(im.getWidth(), im.getHeight()); + } + image = im; + getImagePixels(); // convert to correct format if necessary + analyzePixels(); // build color table & map pixels + if (firstFrame) { + writeLSD(); // logical screen descriptior + writePalette(); // global color table + if (repeat >= 0) { + // use NS app extension to indicate reps + writeNetscapeExt(); + } + } + writeGraphicCtrlExt(); // write graphic control extension + writeImageDesc(); // image descriptor + if (!firstFrame) { + writePalette(); // local color table + } + writePixels(); // encode and write pixel data + firstFrame = false; + } catch (IOException e) { + ok = false; + } + + return ok; + } + + /** + * Flushes any pending data and closes output file. If writing to an + * OutputStream, the stream is not closed. + * @return true if data output closed + */ + public boolean finish() { + if (!started) + return false; + boolean ok = true; + started = false; + try { + out.write(0x3b); // gif trailer + out.flush(); + if (closeStream) { + out.close(); + } + } catch (IOException e) { + ok = false; + } + + // reset for subsequent use + transIndex = 0; + out = null; + image = null; + pixels = null; + indexedPixels = null; + colorTab = null; + closeStream = false; + firstFrame = true; + + return ok; + } + + /** + * Sets frame rate in frames per second. Equivalent to + * setDelay(1000/fps). + * + * @param fps + * float frame rate (frames per second) + */ + public void setFrameRate(float fps) { + if (fps != 0f) { + delay = Math.round(100f / fps); + } + } + + /** + * Sets quality of color quantization (conversion of images to the maximum 256 + * colors allowed by the GIF specification). Lower values (minimum = 1) + * produce better colors, but slow processing significantly. 10 is the + * default, and produces good color mapping at reasonable speeds. Values + * greater than 20 do not yield significant improvements in speed. + * + * @param quality + * int greater than 0. + */ + public void setQuality(int quality) { + if (quality < 1) + quality = 1; + sample = quality; + } + + /** + * Sets the GIF frame size. The default size is the size of the first frame + * added if this method is not invoked. + * + * @param w + * int frame width. + * @param h + * int frame width. + */ + public void setSize(int w, int h) { + if (started && !firstFrame) + return; + width = w; + height = h; + if (width < 1) + width = 320; + if (height < 1) + height = 240; + sizeSet = true; + } + + /** + * Initiates GIF file creation on the given stream. The stream is not closed + * automatically. + * + * @param os + * OutputStream on which GIF images are written. + * @return false if initial write failed. + */ + public boolean start(OutputStream os) { + if (os == null) + return false; + boolean ok = true; + closeStream = false; + out = os; + try { + writeString("GIF89a"); // header + } catch (IOException e) { + ok = false; + } + return started = ok; + } + + /** + * Initiates writing of a GIF file with the specified name. + * + * @param file + * String containing output file name. + * @return false if open or initial write failed. + */ + public boolean start(String file) { + boolean ok = true; + try { + out = new BufferedOutputStream(new FileOutputStream(file)); + ok = start(out); + closeStream = true; + } catch (IOException e) { + ok = false; + } + return started = ok; + } + + /** + * Analyzes image colors and creates color map. + */ + protected void analyzePixels() { + int len = pixels.length; + int nPix = len / 3; + indexedPixels = new byte[nPix]; + NeuQuant nq = new NeuQuant(pixels, len, sample); + // initialize quantizer + colorTab = nq.process(); // create reduced palette + // convert map from BGR to RGB + for (int i = 0; i < colorTab.length; i += 3) { + byte temp = colorTab[i]; + colorTab[i] = colorTab[i + 2]; + colorTab[i + 2] = temp; + usedEntry[i / 3] = false; + } + // map image pixels to new palette + int k = 0; + for (int i = 0; i < nPix; i++) { + int index = nq.map(pixels[k++] & 0xff, pixels[k++] & 0xff, pixels[k++] & 0xff); + usedEntry[index] = true; + indexedPixels[i] = (byte) index; + } + pixels = null; + colorDepth = 8; + palSize = 7; + // get closest match to transparent color if specified + if (transparent != null) { + transIndex = findClosest(transparent); + } + } + + /** + * Returns index of palette color closest to c + * + */ + protected int findClosest(Color c) { + if (colorTab == null) + return -1; + int r = c.getRed(); + int g = c.getGreen(); + int b = c.getBlue(); + int minpos = 0; + int dmin = 256 * 256 * 256; + int len = colorTab.length; + for (int i = 0; i < len;) { + int dr = r - (colorTab[i++] & 0xff); + int dg = g - (colorTab[i++] & 0xff); + int db = b - (colorTab[i] & 0xff); + int d = dr * dr + dg * dg + db * db; + int index = i / 3; + if (usedEntry[index] && (d < dmin)) { + dmin = d; + minpos = index; + } + i++; + } + return minpos; + } + + /** + * Extracts image pixels into byte array "pixels" + */ + protected void getImagePixels() { + int w = image.getWidth(); + int h = image.getHeight(); + int type = image.getType(); + if ((w != width) || (h != height) || (type != BufferedImage.TYPE_3BYTE_BGR)) { + // create new image with right size/format + BufferedImage temp = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR); + Graphics2D g = temp.createGraphics(); + g.drawImage(image, 0, 0, null); + image = temp; + } + pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData(); + } + + /** + * Writes Graphic Control Extension + */ + protected void writeGraphicCtrlExt() throws IOException { + out.write(0x21); // extension introducer + out.write(0xf9); // GCE label + out.write(4); // data block size + int transp, disp; + if (transparent == null) { + transp = 0; + disp = 0; // dispose = no action + } else { + transp = 1; + disp = 2; // force clear if using transparent color + } + if (dispose >= 0) { + disp = dispose & 7; // user override + } + disp <<= 2; + + // packed fields + out.write(0 | // 1:3 reserved + disp | // 4:6 disposal + 0 | // 7 user input - 0 = none + transp); // 8 transparency flag + + writeShort(delay); // delay x 1/100 sec + out.write(transIndex); // transparent color index + out.write(0); // block terminator + } + + /** + * Writes Image Descriptor + */ + protected void writeImageDesc() throws IOException { + out.write(0x2c); // image separator + writeShort(0); // image position x,y = 0,0 + writeShort(0); + writeShort(width); // image size + writeShort(height); + // packed fields + if (firstFrame) { + // no LCT - GCT is used for first (or only) frame + out.write(0); + } else { + // specify normal LCT + out.write(0x80 | // 1 local color table 1=yes + 0 | // 2 interlace - 0=no + 0 | // 3 sorted - 0=no + 0 | // 4-5 reserved + palSize); // 6-8 size of color table + } + } + + /** + * Writes Logical Screen Descriptor + */ + protected void writeLSD() throws IOException { + // logical screen size + writeShort(width); + writeShort(height); + // packed fields + out.write((0x80 | // 1 : global color table flag = 1 (gct used) + 0x70 | // 2-4 : color resolution = 7 + 0x00 | // 5 : gct sort flag = 0 + palSize)); // 6-8 : gct size + + out.write(0); // background color index + out.write(0); // pixel aspect ratio - assume 1:1 + } + + /** + * Writes Netscape application extension to define repeat count. + */ + protected void writeNetscapeExt() throws IOException { + out.write(0x21); // extension introducer + out.write(0xff); // app extension label + out.write(11); // block size + writeString("NETSCAPE" + "2.0"); // app id + auth code + out.write(3); // sub-block size + out.write(1); // loop sub-block id + writeShort(repeat); // loop count (extra iterations, 0=repeat forever) + out.write(0); // block terminator + } + + /** + * Writes color table + */ + protected void writePalette() throws IOException { + out.write(colorTab, 0, colorTab.length); + int n = (3 * 256) - colorTab.length; + for (int i = 0; i < n; i++) { + out.write(0); + } + } + + /** + * Encodes and writes pixel data + */ + protected void writePixels() throws IOException { + LZWEncoder encoder = new LZWEncoder(width, height, indexedPixels, colorDepth); + encoder.encode(out); + } + + /** + * Write 16-bit value to output stream, LSB first + */ + protected void writeShort(int value) throws IOException { + out.write(value & 0xff); + out.write((value >> 8) & 0xff); + } + + /** + * Writes string to output stream + */ + protected void writeString(String s) throws IOException { + for (int i = 0; i < s.length(); i++) { + out.write((byte) s.charAt(i)); + } + } +} + +/* + * NeuQuant Neural-Net Quantization Algorithm + * ------------------------------------------ + * + * Copyright (c) 1994 Anthony Dekker + * + * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See + * "Kohonen neural networks for optimal colour quantization" in "Network: + * Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of + * the algorithm. + * + * Any party obtaining a copy of these files from the author, directly or + * indirectly, is granted, free of charge, a full and unrestricted irrevocable, + * world-wide, paid up, royalty-free, nonexclusive right and license to deal in + * this software and documentation files (the "Software"), including without + * limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons who + * receive copies from any such party to do so, with the only requirement being + * that this copyright notice remain intact. + */ + +// Ported to Java 12/00 K Weiner +class NeuQuant { + + protected static final int netsize = 256; /* number of colours used */ + + /* four primes near 500 - assume no image has a length so large */ + /* that it is divisible by all four primes */ + protected static final int prime1 = 499; + + protected static final int prime2 = 491; + + protected static final int prime3 = 487; + + protected static final int prime4 = 503; + + protected static final int minpicturebytes = (3 * prime4); + + /* minimum size for input image */ + + /* + * Program Skeleton ---------------- [select samplefac in range 1..30] [read + * image from input file] pic = (unsigned char*) malloc(3*width*height); + * initnet(pic,3*width*height,samplefac); learn(); unbiasnet(); [write output + * image header, using writecolourmap(f)] inxbuild(); write output image using + * inxsearch(b,g,r) + */ + + /* + * Network Definitions ------------------- + */ + + protected static final int maxnetpos = (netsize - 1); + + protected static final int netbiasshift = 4; /* bias for colour values */ + + protected static final int ncycles = 100; /* no. of learning cycles */ + + /* defs for freq and bias */ + protected static final int intbiasshift = 16; /* bias for fractions */ + + protected static final int intbias = 1 << intbiasshift; + + protected static final int gammashift = 10; /* gamma = 1024 */ + + protected static final int gamma = 1 << gammashift; + + protected static final int betashift = 10; + + protected static final int beta = (intbias >> betashift); /* beta = 1/1024 */ + + protected static final int betagamma = (intbias << (gammashift - betashift)); + + /* defs for decreasing radius factor */ + protected static final int initrad = (netsize >> 3); /* + * for 256 cols, radius + * starts + */ + + protected static final int radiusbiasshift = 6; /* at 32.0 biased by 6 bits */ + + protected static final int radiusbias = 1 << radiusbiasshift; + + protected static final int initradius = (initrad * radiusbias); /* + * and + * decreases + * by a + */ + + protected static final int radiusdec = 30; /* factor of 1/30 each cycle */ + + /* defs for decreasing alpha factor */ + protected static final int alphabiasshift = 10; /* alpha starts at 1.0 */ + + protected static final int initalpha = 1 << alphabiasshift; + + protected int alphadec; /* biased by 10 bits */ + + /* radbias and alpharadbias used for radpower calculation */ + protected static final int radbiasshift = 8; + + protected static final int radbias = 1 << radbiasshift; + + protected static final int alpharadbshift = (alphabiasshift + radbiasshift); + + protected static final int alpharadbias = 1 << alpharadbshift; + + /* + * Types and Global Variables -------------------------- + */ + + protected byte[] thepicture; /* the input image itself */ + + protected int lengthcount; /* lengthcount = H*W*3 */ + + protected int samplefac; /* sampling factor 1..30 */ + + // typedef int pixel[4]; /* BGRc */ + protected int[][] network; /* the network itself - [netsize][4] */ + + protected int[] netindex = new int[256]; + + /* for network lookup - really 256 */ + + protected int[] bias = new int[netsize]; + + /* bias and freq arrays for learning */ + protected int[] freq = new int[netsize]; + + protected int[] radpower = new int[initrad]; + + /* radpower for precomputation */ + + /* + * Initialise network in range (0,0,0) to (255,255,255) and set parameters + * ----------------------------------------------------------------------- + */ + public NeuQuant(byte[] thepic, int len, int sample) { + + int i; + int[] p; + + thepicture = thepic; + lengthcount = len; + samplefac = sample; + + network = new int[netsize][]; + for (i = 0; i < netsize; i++) { + network[i] = new int[4]; + p = network[i]; + p[0] = p[1] = p[2] = (i << (netbiasshift + 8)) / netsize; + freq[i] = intbias / netsize; /* 1/netsize */ + bias[i] = 0; + } + } + + public byte[] colorMap() { + byte[] map = new byte[3 * netsize]; + int[] index = new int[netsize]; + for (int i = 0; i < netsize; i++) + index[network[i][3]] = i; + int k = 0; + for (int i = 0; i < netsize; i++) { + int j = index[i]; + map[k++] = (byte) (network[j][0]); + map[k++] = (byte) (network[j][1]); + map[k++] = (byte) (network[j][2]); + } + return map; + } + + /* + * Insertion sort of network and building of netindex[0..255] (to do after + * unbias) + * ------------------------------------------------------------------------------- + */ + public void inxbuild() { + + int i, j, smallpos, smallval; + int[] p; + int[] q; + int previouscol, startpos; + + previouscol = 0; + startpos = 0; + for (i = 0; i < netsize; i++) { + p = network[i]; + smallpos = i; + smallval = p[1]; /* index on g */ + /* find smallest in i..netsize-1 */ + for (j = i + 1; j < netsize; j++) { + q = network[j]; + if (q[1] < smallval) { /* index on g */ + smallpos = j; + smallval = q[1]; /* index on g */ + } + } + q = network[smallpos]; + /* swap p (i) and q (smallpos) entries */ + if (i != smallpos) { + j = q[0]; + q[0] = p[0]; + p[0] = j; + j = q[1]; + q[1] = p[1]; + p[1] = j; + j = q[2]; + q[2] = p[2]; + p[2] = j; + j = q[3]; + q[3] = p[3]; + p[3] = j; + } + /* smallval entry is now in position i */ + if (smallval != previouscol) { + netindex[previouscol] = (startpos + i) >> 1; + for (j = previouscol + 1; j < smallval; j++) + netindex[j] = i; + previouscol = smallval; + startpos = i; + } + } + netindex[previouscol] = (startpos + maxnetpos) >> 1; + for (j = previouscol + 1; j < 256; j++) + netindex[j] = maxnetpos; /* really 256 */ + } + + /* + * Main Learning Loop ------------------ + */ + public void learn() { + + int i, j, b, g, r; + int radius, rad, alpha, step, delta, samplepixels; + byte[] p; + int pix, lim; + + if (lengthcount < minpicturebytes) + samplefac = 1; + alphadec = 30 + ((samplefac - 1) / 3); + p = thepicture; + pix = 0; + lim = lengthcount; + samplepixels = lengthcount / (3 * samplefac); + delta = samplepixels / ncycles; + alpha = initalpha; + radius = initradius; + + rad = radius >> radiusbiasshift; + if (rad <= 1) + rad = 0; + for (i = 0; i < rad; i++) + radpower[i] = alpha * (((rad * rad - i * i) * radbias) / (rad * rad)); + + // fprintf(stderr,"beginning 1D learning: initial radius=%d\n", rad); + + if (lengthcount < minpicturebytes) + step = 3; + else if ((lengthcount % prime1) != 0) + step = 3 * prime1; + else { + if ((lengthcount % prime2) != 0) + step = 3 * prime2; + else { + if ((lengthcount % prime3) != 0) + step = 3 * prime3; + else + step = 3 * prime4; + } + } + + i = 0; + while (i < samplepixels) { + b = (p[pix + 0] & 0xff) << netbiasshift; + g = (p[pix + 1] & 0xff) << netbiasshift; + r = (p[pix + 2] & 0xff) << netbiasshift; + j = contest(b, g, r); + + altersingle(alpha, j, b, g, r); + if (rad != 0) + alterneigh(rad, j, b, g, r); /* alter neighbours */ + + pix += step; + if (pix >= lim) + pix -= lengthcount; + + i++; + if (delta == 0) + delta = 1; + if (i % delta == 0) { + alpha -= alpha / alphadec; + radius -= radius / radiusdec; + rad = radius >> radiusbiasshift; + if (rad <= 1) + rad = 0; + for (j = 0; j < rad; j++) + radpower[j] = alpha * (((rad * rad - j * j) * radbias) / (rad * rad)); + } + } + // fprintf(stderr,"finished 1D learning: final alpha=%f + // !\n",((float)alpha)/initalpha); + } + + /* + * Search for BGR values 0..255 (after net is unbiased) and return colour + * index + * ---------------------------------------------------------------------------- + */ + public int map(int b, int g, int r) { + + int i, j, dist, a, bestd; + int[] p; + int best; + + bestd = 1000; /* biggest possible dist is 256*3 */ + best = -1; + i = netindex[g]; /* index on g */ + j = i - 1; /* start at netindex[g] and work outwards */ + + while ((i < netsize) || (j >= 0)) { + if (i < netsize) { + p = network[i]; + dist = p[1] - g; /* inx key */ + if (dist >= bestd) + i = netsize; /* stop iter */ + else { + i++; + if (dist < 0) + dist = -dist; + a = p[0] - b; + if (a < 0) + a = -a; + dist += a; + if (dist < bestd) { + a = p[2] - r; + if (a < 0) + a = -a; + dist += a; + if (dist < bestd) { + bestd = dist; + best = p[3]; + } + } + } + } + if (j >= 0) { + p = network[j]; + dist = g - p[1]; /* inx key - reverse dif */ + if (dist >= bestd) + j = -1; /* stop iter */ + else { + j--; + if (dist < 0) + dist = -dist; + a = p[0] - b; + if (a < 0) + a = -a; + dist += a; + if (dist < bestd) { + a = p[2] - r; + if (a < 0) + a = -a; + dist += a; + if (dist < bestd) { + bestd = dist; + best = p[3]; + } + } + } + } + } + return (best); + } + + public byte[] process() { + learn(); + unbiasnet(); + inxbuild(); + return colorMap(); + } + + /* + * Unbias network to give byte values 0..255 and record position i to prepare + * for sort + * ----------------------------------------------------------------------------------- + */ + public void unbiasnet() { + + int i; + + for (i = 0; i < netsize; i++) { + network[i][0] >>= netbiasshift; + network[i][1] >>= netbiasshift; + network[i][2] >>= netbiasshift; + network[i][3] = i; /* record colour no */ + } + } + + /* + * Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in + * radpower[|i-j|] + * --------------------------------------------------------------------------------- + */ + protected void alterneigh(int rad, int i, int b, int g, int r) { + + int j, k, lo, hi, a, m; + int[] p; + + lo = i - rad; + if (lo < -1) + lo = -1; + hi = i + rad; + if (hi > netsize) + hi = netsize; + + j = i + 1; + k = i - 1; + m = 1; + while ((j < hi) || (k > lo)) { + a = radpower[m++]; + if (j < hi) { + p = network[j++]; + try { + p[0] -= (a * (p[0] - b)) / alpharadbias; + p[1] -= (a * (p[1] - g)) / alpharadbias; + p[2] -= (a * (p[2] - r)) / alpharadbias; + } catch (Exception e) { + } // prevents 1.3 miscompilation + } + if (k > lo) { + p = network[k--]; + try { + p[0] -= (a * (p[0] - b)) / alpharadbias; + p[1] -= (a * (p[1] - g)) / alpharadbias; + p[2] -= (a * (p[2] - r)) / alpharadbias; + } catch (Exception e) { + } + } + } + } + + /* + * Move neuron i towards biased (b,g,r) by factor alpha + * ---------------------------------------------------- + */ + protected void altersingle(int alpha, int i, int b, int g, int r) { + + /* alter hit neuron */ + int[] n = network[i]; + n[0] -= (alpha * (n[0] - b)) / initalpha; + n[1] -= (alpha * (n[1] - g)) / initalpha; + n[2] -= (alpha * (n[2] - r)) / initalpha; + } + + /* + * Search for biased BGR values ---------------------------- + */ + protected int contest(int b, int g, int r) { + + /* finds closest neuron (min dist) and updates freq */ + /* finds best neuron (min dist-bias) and returns position */ + /* for frequently chosen neurons, freq[i] is high and bias[i] is negative */ + /* bias[i] = gamma*((1/netsize)-freq[i]) */ + + int i, dist, a, biasdist, betafreq; + int bestpos, bestbiaspos, bestd, bestbiasd; + int[] n; + + bestd = ~(1 << 31); + bestbiasd = bestd; + bestpos = -1; + bestbiaspos = bestpos; + + for (i = 0; i < netsize; i++) { + n = network[i]; + dist = n[0] - b; + if (dist < 0) + dist = -dist; + a = n[1] - g; + if (a < 0) + a = -a; + dist += a; + a = n[2] - r; + if (a < 0) + a = -a; + dist += a; + if (dist < bestd) { + bestd = dist; + bestpos = i; + } + biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift)); + if (biasdist < bestbiasd) { + bestbiasd = biasdist; + bestbiaspos = i; + } + betafreq = (freq[i] >> betashift); + freq[i] -= betafreq; + bias[i] += (betafreq << gammashift); + } + freq[bestpos] += beta; + bias[bestpos] -= betagamma; + return (bestbiaspos); + } +} + +// ============================================================================== +// Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott. +// K Weiner 12/00 + +class LZWEncoder { + + private static final int EOF = -1; + + private int imgW, imgH; + + private byte[] pixAry; + + private int initCodeSize; + + private int remaining; + + private int curPixel; + + // GIFCOMPR.C - GIF Image compression routines + // + // Lempel-Ziv compression based on 'compress'. GIF modifications by + // David Rowley (mgardi@watdcsu.waterloo.edu) + + // General DEFINEs + + static final int BITS = 12; + + static final int HSIZE = 5003; // 80% occupancy + + // GIF Image compression - modified 'compress' + // + // Based on: compress.c - File compression ala IEEE Computer, June 1984. + // + // By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) + // Jim McKie (decvax!mcvax!jim) + // Steve Davies (decvax!vax135!petsd!peora!srd) + // Ken Turkowski (decvax!decwrl!turtlevax!ken) + // James A. Woods (decvax!ihnp4!ames!jaw) + // Joe Orost (decvax!vax135!petsd!joe) + + int n_bits; // number of bits/code + + int maxbits = BITS; // user settable max # bits/code + + int maxcode; // maximum code, given n_bits + + int maxmaxcode = 1 << BITS; // should NEVER generate this code + + int[] htab = new int[HSIZE]; + + int[] codetab = new int[HSIZE]; + + int hsize = HSIZE; // for dynamic table sizing + + int free_ent = 0; // first unused entry + + // block compression parameters -- after all codes are used up, + // and compression rate changes, start over. + boolean clear_flg = false; + + // Algorithm: use open addressing double hashing (no chaining) on the + // prefix code / next character combination. We do a variant of Knuth's + // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime + // secondary probe. Here, the modular division first probe is gives way + // to a faster exclusive-or manipulation. Also do block compression with + // an adaptive reset, whereby the code table is cleared when the compression + // ratio decreases, but after the table fills. The variable-length output + // codes are re-sized at this point, and a special CLEAR code is generated + // for the decompressor. Late addition: construct the table according to + // file size for noticeable speed improvement on small files. Please direct + // questions about this implementation to ames!jaw. + + int g_init_bits; + + int ClearCode; + + int EOFCode; + + // output + // + // Output the given code. + // Inputs: + // code: A n_bits-bit integer. If == -1, then EOF. This assumes + // that n_bits =< wordsize - 1. + // Outputs: + // Outputs code to the file. + // Assumptions: + // Chars are 8 bits long. + // Algorithm: + // Maintain a BITS character long buffer (so that 8 codes will + // fit in it exactly). Use the VAX insv instruction to insert each + // code in turn. When the buffer fills up empty it and start over. + + int cur_accum = 0; + + int cur_bits = 0; + + int masks[] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, + 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; + + // Number of characters so far in this 'packet' + int a_count; + + // Define the storage for the packet accumulator + byte[] accum = new byte[256]; + + // ---------------------------------------------------------------------------- + LZWEncoder(int width, int height, byte[] pixels, int color_depth) { + imgW = width; + imgH = height; + pixAry = pixels; + initCodeSize = Math.max(2, color_depth); + } + + // Add a character to the end of the current packet, and if it is 254 + // characters, flush the packet to disk. + void char_out(byte c, OutputStream outs) throws IOException { + accum[a_count++] = c; + if (a_count >= 254) + flush_char(outs); + } + + // Clear out the hash table + + // table clear for block compress + void cl_block(OutputStream outs) throws IOException { + cl_hash(hsize); + free_ent = ClearCode + 2; + clear_flg = true; + + output(ClearCode, outs); + } + + // reset code table + void cl_hash(int hsize) { + for (int i = 0; i < hsize; ++i) + htab[i] = -1; + } + + void compress(int init_bits, OutputStream outs) throws IOException { + int fcode; + int i /* = 0 */; + int c; + int ent; + int disp; + int hsize_reg; + int hshift; + + // Set up the globals: g_init_bits - initial number of bits + g_init_bits = init_bits; + + // Set up the necessary values + clear_flg = false; + n_bits = g_init_bits; + maxcode = MAXCODE(n_bits); + + ClearCode = 1 << (init_bits - 1); + EOFCode = ClearCode + 1; + free_ent = ClearCode + 2; + + a_count = 0; // clear packet + + ent = nextPixel(); + + hshift = 0; + for (fcode = hsize; fcode < 65536; fcode *= 2) + ++hshift; + hshift = 8 - hshift; // set hash code range bound + + hsize_reg = hsize; + cl_hash(hsize_reg); // clear hash table + + output(ClearCode, outs); + + outer_loop: while ((c = nextPixel()) != EOF) { + fcode = (c << maxbits) + ent; + i = (c << hshift) ^ ent; // xor hashing + + if (htab[i] == fcode) { + ent = codetab[i]; + continue; + } else if (htab[i] >= 0) // non-empty slot + { + disp = hsize_reg - i; // secondary hash (after G. Knott) + if (i == 0) + disp = 1; + do { + if ((i -= disp) < 0) + i += hsize_reg; + + if (htab[i] == fcode) { + ent = codetab[i]; + continue outer_loop; + } + } while (htab[i] >= 0); + } + output(ent, outs); + ent = c; + if (free_ent < maxmaxcode) { + codetab[i] = free_ent++; // code -> hashtable + htab[i] = fcode; + } else + cl_block(outs); + } + // Put out the final code. + output(ent, outs); + output(EOFCode, outs); + } + + // ---------------------------------------------------------------------------- + void encode(OutputStream os) throws IOException { + os.write(initCodeSize); // write "initial code size" byte + + remaining = imgW * imgH; // reset navigation variables + curPixel = 0; + + compress(initCodeSize + 1, os); // compress and write the pixel data + + os.write(0); // write block terminator + } + + // Flush the packet to disk, and reset the accumulator + void flush_char(OutputStream outs) throws IOException { + if (a_count > 0) { + outs.write(a_count); + outs.write(accum, 0, a_count); + a_count = 0; + } + } + + final int MAXCODE(int n_bits) { + return (1 << n_bits) - 1; + } + + // ---------------------------------------------------------------------------- + // Return the next pixel from the image + // ---------------------------------------------------------------------------- + private int nextPixel() { + if (remaining == 0) + return EOF; + + --remaining; + + byte pix = pixAry[curPixel++]; + + return pix & 0xff; + } + + void output(int code, OutputStream outs) throws IOException { + cur_accum &= masks[cur_bits]; + + if (cur_bits > 0) + cur_accum |= (code << cur_bits); + else + cur_accum = code; + + cur_bits += n_bits; + + while (cur_bits >= 8) { + char_out((byte) (cur_accum & 0xff), outs); + cur_accum >>= 8; + cur_bits -= 8; + } + + // If the next entry is going to be too big for the code size, + // then increase it, if possible. + if (free_ent > maxcode || clear_flg) { + if (clear_flg) { + maxcode = MAXCODE(n_bits = g_init_bits); + clear_flg = false; + } else { + ++n_bits; + if (n_bits == maxbits) + maxcode = maxmaxcode; + else + maxcode = MAXCODE(n_bits); + } + } + + if (code == EOFCode) { + // At EOF, write the rest of the buffer. + while (cur_bits > 0) { + char_out((byte) (cur_accum & 0xff), outs); + cur_accum >>= 8; + cur_bits -= 8; + } + + flush_char(outs); + } + } +} \ No newline at end of file diff --git a/libraries/gifAnimation/src/GifMaker.java b/libraries/gifAnimation/src/GifMaker.java new file mode 100755 index 0000000..e0a0c6b --- /dev/null +++ b/libraries/gifAnimation/src/GifMaker.java @@ -0,0 +1,168 @@ +/* + * GifAnimation is a processing library to play gif animations and to + * extract frames from a gif file. It can also export animated GIF animations + * This file class is under a GPL license. The Decoder used to open the + * gif files was written by Kevin Weiner. please see the separate copyright + * notice in the header of the GifDecoder / GifEncoder class. + * + * by extrapixel 2007 + * http://extrapixel.ch + * + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +package gifAnimation; + +import java.awt.Color; +import java.awt.image.BufferedImage; +import processing.core.PApplet; +import processing.core.PConstants; +import processing.core.PImage; + +public class GifMaker implements PConstants { + public final static int DISPOSE_NOTHING = 0; + public final static int DISPOSE_KEEP = 1; + public final static int DISPOSE_RESTORE_BACKGROUND = 2; + public final static int DISPOSE_REMOVE = 3; + private GifEncoder encoder; + private PApplet parent; + + public GifMaker(PApplet parent, String filename) { + this.parent = parent; + parent.registerMethod("dispose", this); + encoder = initEncoder(filename); + } + + public GifMaker(PApplet parent, String filename, int quality) { + this(parent, filename); + setQuality(quality); + } + + public GifMaker(PApplet parent, String filename, int quality, int bgColor) { + this(parent, filename, quality); + setTransparent(bgColor); + } + + /* + * finish stuff up when sketch is killed + */ + public void dispose() { + finish(); + } + + private GifEncoder initEncoder(String filename) { + GifEncoder returnEncoder = new GifEncoder(); + returnEncoder.start(parent.savePath(filename)); + return returnEncoder; + } + + /* + * adds a delay to the last added frame int in milliseconds + */ + public void setDelay(int delay) { + encoder.setDelay(delay); + } + + /* + * set the disposal mode for the last added frame + * + * from GIF specs: CODE MEANING 00 Nothing special 01 KEEP - retain the + * current image 02 RESTORE BACKGROUND - restore the background color 03 + * REMOVE - remove the current image, and restore whatever image was beneath + * it. + */ + public void setDispose(int dispose) { + encoder.setDispose(dispose); + } + + /** + * description taken from GifEncoder-class: Sets quality of color + * quantization (conversion of images to the maximum 256 colors allowed by + * the GIF specification). Lower values (minimum = 1) produce better colors, + * but slow processing significantly. 10 is the default, and produces good + * color mapping at reasonable speeds. Values greater than 20 do not yield + * significant improvements in speed. + * + * @param quality + * int greater than 0. + */ + public void setQuality(int quality) { + encoder.setQuality(quality); + } + + /* + * sets the amount of times the animation should repeat + * + */ + public void setRepeat(int repeat) { + encoder.setRepeat(repeat); + } + + /* + * sets the size of the GIF-file. if this method is not invoked, the size of + * the first added frame will be the image size. + */ + public void setSize(int width, int height) { + encoder.setSize(width, height); + } + + /* + * Sets the transparent color. Every pixel with this color will be + * transparent in the output File + */ + public void setTransparent(int color) { + setTransparent((int) parent.red(color), (int) parent.green(color), + (int) parent.blue(color)); + } + + public void setTransparent(float red, float green, float blue) { + setTransparent((int) red, (int) green, (int) blue); + } + + public void setTransparent(int red, int green, int blue) { + encoder.setTransparent(new Color(red, green, blue)); + } + + /* + * adds a frame to the current animation takes a PImage, or a pixel-array. + * if no parameter is passed, the currently displayed pixels in the sketch + * window is used. + */ + public void addFrame() { + parent.loadPixels(); + addFrame(parent.pixels, parent.pixelWidth, parent.pixelHeight); + } + + public void addFrame(PImage newImage) { + addFrame(newImage.pixels, newImage.width, newImage.height); + } + + public void addFrame(int[] pixels, int width, int height) { + BufferedImage frame = new BufferedImage(width, height, + BufferedImage.TYPE_INT_ARGB); + frame.setRGB(0, 0, width, height, pixels, 0, width); + encoder.addFrame(frame); + } + + /* + * finishes off the GIF-file and saves it to the given filename + * in the sketch directory. if the file already exists, it will + * be overridden! + */ + public boolean finish() { + return encoder.finish(); + } + +}