no commit message

This commit is contained in:
niandeng
2020-09-17 18:40:01 +08:00
parent 4382915d09
commit d957abceaa
283 changed files with 86935 additions and 63 deletions
@@ -0,0 +1,514 @@
/**
* This class lets you encode animated GIF files
* Base class : http://www.java2s.com/Code/Java/2D-Graphics-GUI/AnimatedGifEncoder.htm
* @author Kevin Weiner (original Java version - kweiner@fmsware.com)
* @author Thibault Imbert (AS3 version - bytearray.org)
* @version 0.1 AS3 implementation
*/
package org.bytearray.gif.encoder
{
import flash.utils.ByteArray;
import flash.display.BitmapData;
import flash.display.Bitmap;
import org.bytearray.gif.encoder.NeuQuant
import flash.net.URLRequestHeader;
import flash.net.URLRequestMethod;
import flash.net.URLRequest;
import flash.net.navigateToURL;
public class GIFEncoder
{
private var width:int // image size
private var height:int;
private var transparent:* = null; // transparent color if given
private var transIndex:int; // transparent index in color table
private var repeat:int = -1; // no repeat
private var delay:int = 0; // frame delay (hundredths)
private var started:Boolean = false; // ready to output frames
private var out:ByteArray;
private var image:Bitmap; // current frame
private var pixels:ByteArray; // BGR byte array from frame
private var indexedPixels:ByteArray // converted frame indexed to palette
private var colorDepth:int; // number of bit planes
private var colorTab:ByteArray; // RGB palette
private var usedEntry:Array = new Array; // active palette entries
private var palSize:int = 7; // color table size (bits-1)
private var dispose:int = -1; // disposal code (-1 = use default)
private var closeStream:Boolean = false; // close stream when finished
private var firstFrame:Boolean = true;
private var sizeSet:Boolean = false; // if false, get size from first frame
private var sample:int = 10; // default sample interval for quantizer
/**
* Sets the delay time between each frame, or changes it for subsequent frames
* (applies to last frame added)
* int delay time in milliseconds
* @param ms
*/
public function setDelay(ms:int):void
{
delay = Math.round(ms / 10);
}
/**
* 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 function setDispose(code:int):void
{
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.
* @return
*/
public function setRepeat(iter:int):void
{
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
* Color to be treated as transparent on display.
*/
public function setTransparent(c:Number):void
{
transparent = c;
}
/**
* The addFrame method takes an incoming BitmapData object to create each frames
* @param
* BitmapData object to be treated as a GIF's frame
*/
public function addFrame(im:BitmapData):Boolean
{
if ((im == null) || !started || out == null)
{
throw new Error ("Please call start method before calling addFrame");
return false;
}
var ok:Boolean = true;
try {
image = new Bitmap ( im );
if (!sizeSet) setSize(image.width, image.height);
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 (e:Error) {
ok = false;
}
return ok;
}
/**
* Adds final trailer to the GIF stream, if you don't call the finish method
* the GIF stream will not be valid.
*/
public function finish():Boolean
{
if (!started) return false;
var ok:Boolean = true;
started = false;
try {
out.writeByte(0x3b); // gif trailer
} catch (e:Error) {
ok = false;
}
return ok;
}
/**
* Resets some members so that a new stream can be started.
* This method is actually called by the start method
*/
private function reset ( ):void
{
// reset for subsequent use
transIndex = 0;
image = null;
pixels = null;
indexedPixels = null;
colorTab = null;
closeStream = false;
firstFrame = true;
}
/**
* * Sets frame rate in frames per second. Equivalent to
* <code>setDelay(1000/fps)</code>.
* @param fps
* float frame rate (frames per second)
*/
public function setFrameRate(fps:Number):void
{
if (fps != 0xf) delay = Math.round(100/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.
* @return
*/
public function setQuality(quality:int):void
{
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.
*/
private function setSize(w:int, h:int):void
{
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.
* @param os
* OutputStream on which GIF images are written.
* @return false if initial write failed.
*
*/
public function start():Boolean
{
reset();
var ok:Boolean = true;
closeStream = false;
out = new ByteArray;
try {
out.writeUTFBytes("GIF89a"); // header
} catch (e:Error) {
ok = false;
}
return started = ok;
}
/**
* Analyzes image colors and creates color map.
*/
private function analyzePixels():void
{
var len:int = pixels.length;
var nPix:int = len / 3;
indexedPixels = new ByteArray;
var nq:NeuQuant = new NeuQuant(pixels, len, sample);
// initialize quantizer
colorTab = nq.process(); // create reduced palette
// map image pixels to new palette
var k:int = 0;
for (var j:int = 0; j < nPix; j++) {
var index:int = nq.map(pixels[k++] & 0xff, pixels[k++] & 0xff, pixels[k++] & 0xff);
usedEntry[index] = true;
indexedPixels[j] = 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
*
*/
private function findClosest(c:Number):int
{
if (colorTab == null) return -1;
var r:int = (c & 0xFF0000) >> 16;
var g:int = (c & 0x00FF00) >> 8;
var b:int = (c & 0x0000FF);
var minpos:int = 0;
var dmin:int = 256 * 256 * 256;
var len:int = colorTab.length;
for (var i:int = 0; i < len;) {
var dr:int = r - (colorTab[i++] & 0xff);
var dg:int = g - (colorTab[i++] & 0xff);
var db:int = b - (colorTab[i] & 0xff);
var d:int = dr * dr + dg * dg + db * db;
var index:int = i / 3;
if (usedEntry[index] && (d < dmin)) {
dmin = d;
minpos = index;
}
i++;
}
return minpos;
}
/**
* Extracts image pixels into byte array "pixels
*/
private function getImagePixels():void
{
var w:int = image.width;
var h:int = image.height;
pixels = new ByteArray;
var count:int = 0;
for ( var i:int = 0; i < h; i++ )
{
for (var j:int = 0; j < w; j++ )
{
var pixel:Number = image.bitmapData.getPixel( j, i );
pixels[count] = (pixel & 0xFF0000) >> 16;
count++;
pixels[count] = (pixel & 0x00FF00) >> 8;
count++;
pixels[count] = (pixel & 0x0000FF);
count++;
}
}
}
/**
* Writes Graphic Control Extension
*/
private function writeGraphicCtrlExt():void
{
out.writeByte(0x21); // extension introducer
out.writeByte(0xf9); // GCE label
out.writeByte(4); // data block size
var transp:int
var disp:int;
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.writeByte(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.writeByte(transIndex); // transparent color index
out.writeByte(0); // block terminator
}
/**
* Writes Image Descriptor
*/
private function writeImageDesc():void
{
out.writeByte(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.writeByte(0);
} else {
// specify normal LCT
out.writeByte(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
*/
private function writeLSD():void
{
// logical screen size
WriteShort(width);
WriteShort(height);
// packed fields
out.writeByte((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.writeByte(0); // background color index
out.writeByte(0); // pixel aspect ratio - assume 1:1
}
/**
* Writes Netscape application extension to define repeat count.
*/
private function writeNetscapeExt():void
{
out.writeByte(0x21); // extension introducer
out.writeByte(0xff); // app extension label
out.writeByte(11); // block size
out.writeUTFBytes("NETSCAPE" + "2.0"); // app id + auth code
out.writeByte(3); // sub-block size
out.writeByte(1); // loop sub-block id
WriteShort(repeat); // loop count (extra iterations, 0=repeat forever)
out.writeByte(0); // block terminator
}
/**
* Writes color table
*/
private function writePalette():void
{
out.writeBytes(colorTab, 0, colorTab.length);
var n:int = (3 * 256) - colorTab.length;
for (var i:int = 0; i < n; i++) out.writeByte(0);
}
private function WriteShort (pValue:int):void
{
out.writeByte( pValue & 0xFF );
out.writeByte( (pValue >> 8) & 0xFF);
}
/**
* Encodes and writes pixel data
*/
private function writePixels():void
{
var myencoder:LZWEncoder = new LZWEncoder(width, height, indexedPixels, colorDepth);
myencoder.encode(out);
}
/**
* retrieves the GIF stream
*/
public function get stream ( ):ByteArray
{
return out;
}
}
}