
com.surenpi.autotest.utils.AnimatedGifEncoder Maven / Gradle / Ivy
The newest version!
/**
* http://surenpi.com
*/
package com.surenpi.autotest.utils;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* Gif图片编码器
* @author suren
* @since 2016年8月4日 下午12:31:31
*/
public class AnimatedGifEncoder
{
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 the
*/
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
* @return sd
*/
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
* @throws IOException IOException
*/
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
* @throws IOException IOException
*/
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
* @throws IOException IOException
*/
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.
* @throws IOException IOException
*/
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
* @throws IOException IOException
*/
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
* @throws IOException IOException
*/
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
* @throws IOException IOException
*/
protected void writeShort(int value) throws IOException
{
out.write(value & 0xff);
out.write((value >> 8) & 0xff);
}
/**
* Writes string to output stream
* @param s s
* @throws IOException IOException
*/
protected void writeString(String s) throws IOException
{
for (int i = 0; i < s.length(); i++)
{
out.write((byte) s.charAt(i));
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy