├── org └── bytearray │ └── gif │ ├── errors │ └── FileTypeError.as │ ├── events │ ├── TimeoutEvent.as │ ├── FileTypeEvent.as │ ├── GIFPlayerEvent.as │ └── FrameEvent.as │ ├── frames │ └── GIFFrame.as │ ├── player │ ├── GIFImage.as │ └── GIFPlayer.as │ ├── encoder │ ├── LZWEncoder.as │ ├── GIFEncoder.as │ └── NeuQuant.as │ └── decoder │ └── GIFDecoder.as ├── README └── LICENSE /org/bytearray/gif/errors/FileTypeError.as: -------------------------------------------------------------------------------- 1 | package org.bytearray.gif.errors 2 | { 3 | public class FileTypeError extends Error 4 | { 5 | public function FileTypeError ( pMessage:String ) 6 | { 7 | super ( pMessage ); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /org/bytearray/gif/events/TimeoutEvent.as: -------------------------------------------------------------------------------- 1 | package org.bytearray.gif.events 2 | { 3 | import flash.events.Event;; 4 | 5 | public class TimeoutEvent extends Event 6 | { 7 | public static const TIME_OUT:String = "timeout"; 8 | 9 | public function TimeoutEvent ( pType:String ) 10 | { 11 | super ( pType, false, false ); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /org/bytearray/gif/events/FileTypeEvent.as: -------------------------------------------------------------------------------- 1 | package org.bytearray.gif.events 2 | { 3 | import flash.events.Event; 4 | 5 | public class FileTypeEvent extends Event 6 | { 7 | public static const INVALID:String = "invalid"; 8 | 9 | public function FileTypeEvent ( pType:String ) 10 | { 11 | super ( pType, false, false ); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /org/bytearray/gif/frames/GIFFrame.as: -------------------------------------------------------------------------------- 1 | package org.bytearray.gif.frames 2 | { 3 | import flash.display.BitmapData; 4 | 5 | public class GIFFrame 6 | { 7 | public var bitmapData:BitmapData; 8 | public var delay:int; 9 | public var dispose:int; 10 | 11 | public function GIFFrame( pImage:BitmapData, pDelay:int, pDispose:int ) 12 | { 13 | bitmapData = pImage; 14 | delay = pDelay; 15 | dispose = pDispose; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /org/bytearray/gif/events/GIFPlayerEvent.as: -------------------------------------------------------------------------------- 1 | package org.bytearray.gif.events 2 | { 3 | import flash.events.Event; 4 | import flash.geom.Rectangle; 5 | 6 | public class GIFPlayerEvent extends Event 7 | { 8 | public var rect:Rectangle; 9 | 10 | public static const COMPLETE:String = "complete"; 11 | 12 | public function GIFPlayerEvent ( pType:String, pRect:Rectangle ) 13 | { 14 | super ( pType, false, false ); 15 | rect = pRect; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /org/bytearray/gif/events/FrameEvent.as: -------------------------------------------------------------------------------- 1 | package org.bytearray.gif.events 2 | { 3 | import flash.events.Event; 4 | import org.bytearray.gif.frames.GIFFrame; 5 | 6 | public class FrameEvent extends Event 7 | { 8 | public var frame:GIFFrame; 9 | 10 | public static const FRAME_RENDERED:String = "rendered"; 11 | 12 | public function FrameEvent ( pType:String, pFrame:GIFFrame ) 13 | { 14 | super ( pType, false, false ); 15 | 16 | frame = pFrame; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | This is a fork of the MIT-licensed as3gif project from: 2 | 3 | http://code.google.com/p/as3gif/ 4 | 5 | The original author is Thibault Imbert: 6 | 7 | http://www.bytearray.org/?p=95 8 | 9 | Modifications to the original author's code are placed under the CC0 declaration: 10 | 11 | To the extent possible under law, 唐鳳 has waived all copyright and related 12 | or neighboring rights to as3gif. 13 | 14 | This work is published from Taiwan. 15 | 16 | 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007-2010 Thibault Imbert 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /org/bytearray/gif/player/GIFImage.as: -------------------------------------------------------------------------------- 1 | package org.bytearray.gif.player 2 | { 3 | import flash.display.DisplayObject; 4 | import flash.events.Event; 5 | import flash.net.URLRequest; 6 | 7 | import mx.controls.Image; 8 | import mx.core.UIComponent; 9 | 10 | import org.bytearray.gif.events.FileTypeEvent; 11 | import org.bytearray.gif.events.GIFPlayerEvent; 12 | 13 | public class GIFImage extends UIComponent 14 | { 15 | private var _player:GIFPlayer = null; 16 | private var _image:Image = null; 17 | private var _source:String = null; 18 | private var _child:DisplayObject = null; 19 | 20 | private function setChild(child:DisplayObject):void { 21 | if (_child === child) return; 22 | if (_child) removeChild(_child); 23 | addChild(child); 24 | _child = child; 25 | } 26 | 27 | public function get source ():String { 28 | return _source; 29 | } 30 | 31 | public function set source (src:String):void { 32 | if (_source === src) return; 33 | 34 | if (_player) { 35 | _player.stop(); 36 | _player.bitmapData.dispose(); 37 | } 38 | 39 | _source = src; 40 | 41 | if (/\.gif$/i.test(src)) { 42 | doLoadPlayer(); 43 | } 44 | else { 45 | doLoadImage(); 46 | } 47 | } 48 | 49 | private function doLoadPlayer (event:Event=null):void { 50 | if (!_player) { 51 | _player = new GIFPlayer(); 52 | _player.addEventListener(GIFPlayerEvent.COMPLETE, onCompleteGIF); 53 | _player.addEventListener(FileTypeEvent.INVALID, doLoadImage); 54 | } 55 | setChild(_player); 56 | _player.load(new URLRequest(_source)); 57 | } 58 | 59 | private function doLoadImage (event:Event=null):void { 60 | if (!_image) { 61 | _image = new Image(); 62 | _image.addEventListener(Event.COMPLETE, onCompleteImage); 63 | } 64 | setChild(_image); 65 | _image.source = _source; 66 | } 67 | 68 | private function onCompleteImage(event:Event):void { 69 | this.width = _image.width = _image.contentWidth; 70 | this.height = _image.height = _image.contentHeight; 71 | this.invalidateSize(); 72 | this.validateNow(); 73 | } 74 | 75 | private function onCompleteGIF(event:GIFPlayerEvent):void { 76 | this.width = event.rect.width; 77 | this.height = event.rect.height; 78 | this.invalidateSize(); 79 | this.validateNow(); 80 | _player.play(); 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /org/bytearray/gif/encoder/LZWEncoder.as: -------------------------------------------------------------------------------- 1 | /** 2 | * This class handles LZW encoding 3 | * Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott. 4 | * @author Kevin Weiner (original Java version - kweiner@fmsware.com) 5 | * @author Thibault Imbert (AS3 version - bytearray.org) 6 | * @version 0.1 AS3 implementation 7 | */ 8 | 9 | package org.bytearray.gif.encoder 10 | { 11 | import flash.utils.ByteArray; 12 | 13 | public class LZWEncoder 14 | { 15 | private static var EOF:int = -1; 16 | private var imgW:int; 17 | private var imgH:int 18 | private var pixAry:ByteArray; 19 | private var initCodeSize:int; 20 | private var remaining:int; 21 | private var curPixel:int; 22 | 23 | // GIFCOMPR.C - GIF Image compression routines 24 | // Lempel-Ziv compression based on 'compress'. GIF modifications by 25 | // David Rowley (mgardi@watdcsu.waterloo.edu) 26 | // General DEFINEs 27 | 28 | private static var BITS:int = 12; 29 | private static var HSIZE:int = 5003; // 80% occupancy 30 | 31 | // GIF Image compression - modified 'compress' 32 | // Based on: compress.c - File compression ala IEEE Computer, June 1984. 33 | // By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) 34 | // Jim McKie (decvax!mcvax!jim) 35 | // Steve Davies (decvax!vax135!petsd!peora!srd) 36 | // Ken Turkowski (decvax!decwrl!turtlevax!ken) 37 | // James A. Woods (decvax!ihnp4!ames!jaw) 38 | // Joe Orost (decvax!vax135!petsd!joe) 39 | 40 | private var n_bits:int // number of bits/code 41 | private var maxbits:int = BITS; // user settable max # bits/code 42 | private var maxcode:int // maximum code, given n_bits 43 | private var maxmaxcode:int = 1 << BITS; // should NEVER generate this code 44 | private var htab:Array = new Array; 45 | private var codetab:Array = new Array; 46 | private var hsize:int = HSIZE; // for dynamic table sizing 47 | private var free_ent:int = 0; // first unused entry 48 | 49 | // block compression parameters -- after all codes are used up, 50 | // and compression rate changes, start over. 51 | 52 | private var clear_flg:Boolean = false; 53 | 54 | // Algorithm: use open addressing double hashing (no chaining) on the 55 | // prefix code / next character combination. We do a variant of Knuth's 56 | // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime 57 | // secondary probe. Here, the modular division first probe is gives way 58 | // to a faster exclusive-or manipulation. Also do block compression with 59 | // an adaptive reset, whereby the code table is cleared when the compression 60 | // ratio decreases, but after the table fills. The variable-length output 61 | // codes are re-sized at this point, and a special CLEAR code is generated 62 | // for the decompressor. Late addition: construct the table according to 63 | // file size for noticeable speed improvement on small files. Please direct 64 | // questions about this implementation to ames!jaw. 65 | 66 | private var g_init_bits:int; 67 | private var ClearCode:int; 68 | private var EOFCode:int; 69 | 70 | // output 71 | // Output the given code. 72 | // Inputs: 73 | // code: A n_bits-bit integer. If == -1, then EOF. This assumes 74 | // that n_bits =< wordsize - 1. 75 | // Outputs: 76 | // Outputs code to the file. 77 | // Assumptions: 78 | // Chars are 8 bits long. 79 | // Algorithm: 80 | // Maintain a BITS character long buffer (so that 8 codes will 81 | // fit in it exactly). Use the VAX insv instruction to insert each 82 | // code in turn. When the buffer fills up empty it and start over. 83 | 84 | private var cur_accum:int = 0; 85 | private var cur_bits:int = 0; 86 | private var masks:Array = [ 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF ]; 87 | 88 | // Number of characters so far in this 'packet' 89 | private var a_count:int; 90 | 91 | // Define the storage for the packet accumulator 92 | private var accum:ByteArray = new ByteArray; 93 | 94 | public function LZWEncoder (width:int, height:int, pixels:ByteArray, color_depth:int) 95 | { 96 | 97 | imgW = width; 98 | imgH = height; 99 | pixAry = pixels; 100 | initCodeSize = Math.max(2, color_depth); 101 | 102 | } 103 | 104 | // Add a character to the end of the current packet, and if it is 254 105 | // characters, flush the packet to disk. 106 | private function char_out(c:Number, outs:ByteArray):void 107 | { 108 | 109 | accum[a_count++] = c; 110 | if (a_count >= 254)flush_char(outs); 111 | 112 | } 113 | 114 | // Clear out the hash table 115 | // table clear for block compress 116 | 117 | private function cl_block(outs:ByteArray):void 118 | { 119 | 120 | cl_hash(hsize); 121 | free_ent = ClearCode + 2; 122 | clear_flg = true; 123 | output(ClearCode, outs); 124 | 125 | } 126 | 127 | // reset code table 128 | private function cl_hash(hsize:int):void 129 | { 130 | 131 | for (var i:int = 0; i < hsize; ++i) htab[i] = -1; 132 | 133 | } 134 | 135 | public function compress(init_bits:int, outs:ByteArray):void 136 | 137 | { 138 | 139 | var fcode:int; 140 | var i:int /* = 0 */; 141 | var c:int; 142 | var ent:int; 143 | var disp:int; 144 | var hsize_reg:int; 145 | var hshift:int; 146 | 147 | // Set up the globals: g_init_bits - initial number of bits 148 | g_init_bits = init_bits; 149 | 150 | // Set up the necessary values 151 | clear_flg = false; 152 | n_bits = g_init_bits; 153 | maxcode = MAXCODE(n_bits); 154 | 155 | ClearCode = 1 << (init_bits - 1); 156 | EOFCode = ClearCode + 1; 157 | free_ent = ClearCode + 2; 158 | 159 | a_count = 0; // clear packet 160 | 161 | ent = nextPixel(); 162 | 163 | hshift = 0; 164 | for (fcode = hsize; fcode < 65536; fcode *= 2) 165 | ++hshift; 166 | hshift = 8 - hshift; // set hash code range bound 167 | 168 | hsize_reg = hsize; 169 | cl_hash(hsize_reg); // clear hash table 170 | 171 | output(ClearCode, outs); 172 | 173 | outer_loop: while ((c = nextPixel()) != EOF) 174 | 175 | { 176 | 177 | fcode = (c << maxbits) + ent; 178 | i = (c << hshift) ^ ent; // xor hashing 179 | 180 | if (htab[i] == fcode) 181 | { 182 | ent = codetab[i]; 183 | continue; 184 | } else if (htab[i] >= 0) // non-empty slot 185 | { 186 | disp = hsize_reg - i; // secondary hash (after G. Knott) 187 | if (i == 0) 188 | disp = 1; 189 | do 190 | { 191 | 192 | if ((i -= disp) < 0) i += hsize_reg; 193 | 194 | if (htab[i] == fcode) 195 | { 196 | ent = codetab[i]; 197 | continue outer_loop; 198 | } 199 | } while (htab[i] >= 0); 200 | } 201 | 202 | output(ent, outs); 203 | ent = c; 204 | if (free_ent < maxmaxcode) 205 | { 206 | codetab[i] = free_ent++; // code -> hashtable 207 | htab[i] = fcode; 208 | } else cl_block(outs); 209 | } 210 | 211 | // Put out the final code. 212 | output(ent, outs); 213 | output(EOFCode, outs); 214 | 215 | } 216 | 217 | // ---------------------------------------------------------------------------- 218 | public function encode(os:ByteArray):void 219 | { 220 | 221 | os.writeByte(initCodeSize); // write "initial code size" byte 222 | remaining = imgW * imgH; // reset navigation variables 223 | curPixel = 0; 224 | compress(initCodeSize + 1, os); // compress and write the pixel data 225 | os.writeByte(0); // write block terminator 226 | 227 | } 228 | 229 | // Flush the packet to disk, and reset the accumulator 230 | private function flush_char(outs:ByteArray):void 231 | { 232 | 233 | if (a_count > 0) 234 | { 235 | outs.writeByte(a_count); 236 | outs.writeBytes(accum, 0, a_count); 237 | a_count = 0; 238 | } 239 | 240 | } 241 | 242 | private function MAXCODE(n_bits:int):int 243 | { 244 | 245 | return (1 << n_bits) - 1; 246 | 247 | } 248 | 249 | // ---------------------------------------------------------------------------- 250 | // Return the next pixel from the image 251 | // ---------------------------------------------------------------------------- 252 | 253 | private function nextPixel():int 254 | { 255 | 256 | if (remaining == 0) return EOF; 257 | 258 | --remaining; 259 | 260 | var pix:Number = pixAry[curPixel++]; 261 | 262 | return pix & 0xff; 263 | 264 | } 265 | 266 | private function output(code:int, outs:ByteArray):void 267 | 268 | { 269 | 270 | cur_accum &= masks[cur_bits]; 271 | 272 | if (cur_bits > 0) cur_accum |= (code << cur_bits); 273 | else cur_accum = code; 274 | 275 | cur_bits += n_bits; 276 | 277 | while (cur_bits >= 8) 278 | 279 | { 280 | 281 | char_out((cur_accum & 0xff), outs); 282 | cur_accum >>= 8; 283 | cur_bits -= 8; 284 | 285 | } 286 | 287 | // If the next entry is going to be too big for the code size, 288 | // then increase it, if possible. 289 | 290 | if (free_ent > maxcode || clear_flg) 291 | { 292 | 293 | if (clear_flg) 294 | { 295 | 296 | maxcode = MAXCODE(n_bits = g_init_bits); 297 | clear_flg = false; 298 | 299 | } else 300 | { 301 | 302 | ++n_bits; 303 | 304 | if (n_bits == maxbits) maxcode = maxmaxcode; 305 | 306 | else maxcode = MAXCODE(n_bits); 307 | 308 | } 309 | 310 | } 311 | 312 | if (code == EOFCode) 313 | { 314 | 315 | // At EOF, write the rest of the buffer. 316 | while (cur_bits > 0) 317 | { 318 | 319 | char_out((cur_accum & 0xff), outs); 320 | cur_accum >>= 8; 321 | cur_bits -= 8; 322 | } 323 | 324 | 325 | flush_char(outs); 326 | 327 | } 328 | 329 | } 330 | 331 | } 332 | 333 | } -------------------------------------------------------------------------------- /org/bytearray/gif/player/GIFPlayer.as: -------------------------------------------------------------------------------- 1 | /** 2 | * This class lets you play animated GIF files in AS3 3 | * @author Thibault Imbert (bytearray.org) 4 | * @version 0.6 5 | */ 6 | 7 | package org.bytearray.gif.player 8 | { 9 | import flash.display.Bitmap; 10 | import flash.display.BitmapData; 11 | import flash.errors.ScriptTimeoutError; 12 | import flash.events.Event; 13 | import flash.events.IOErrorEvent; 14 | import flash.events.TimerEvent; 15 | import flash.net.URLLoader; 16 | import flash.net.URLLoaderDataFormat; 17 | import flash.net.URLRequest; 18 | import flash.system.LoaderContext; 19 | import flash.utils.ByteArray; 20 | import flash.utils.Timer; 21 | import flash.utils.getTimer; 22 | 23 | import org.bytearray.gif.decoder.GIFDecoder; 24 | import org.bytearray.gif.errors.FileTypeError; 25 | import org.bytearray.gif.events.FileTypeEvent; 26 | import org.bytearray.gif.events.FrameEvent; 27 | import org.bytearray.gif.events.GIFPlayerEvent; 28 | import org.bytearray.gif.events.TimeoutEvent; 29 | import org.bytearray.gif.frames.GIFFrame; 30 | 31 | public class GIFPlayer extends Bitmap 32 | { 33 | private var urlLoader:URLLoader; 34 | private var aFrames:Array; 35 | private var myTimer:Timer; 36 | private var iInc:int; 37 | private var iIndex:int; 38 | private var auto:Boolean; 39 | private var arrayLng:uint; 40 | private var source:String; 41 | private var _loopCount:int; 42 | private static var byteStreamCache:Object = {}; 43 | 44 | public function GIFPlayer ( pAutoPlay:Boolean = true ) 45 | { 46 | auto = pAutoPlay; 47 | iIndex = iInc = 0; 48 | 49 | myTimer = new Timer ( 0, 0 ); 50 | aFrames = new Array(); 51 | urlLoader = new URLLoader(); 52 | urlLoader.dataFormat = URLLoaderDataFormat.BINARY; 53 | 54 | urlLoader.addEventListener ( Event.COMPLETE, onComplete ); 55 | urlLoader.addEventListener ( IOErrorEvent.IO_ERROR, onIOError ); 56 | 57 | myTimer.addEventListener ( TimerEvent.TIMER, update ); 58 | } 59 | 60 | private function onIOError ( pEvt:IOErrorEvent ):void 61 | { 62 | dispatchEvent ( pEvt ); 63 | } 64 | 65 | private function onComplete ( pEvt:Event ):void 66 | { 67 | byteStreamCache[this.source] = ByteArray(pEvt.target.data); 68 | readStream ( pEvt.target.data ); 69 | } 70 | 71 | private function readStream ( pBytes:ByteArray ):void 72 | { 73 | var gifStream:ByteArray = pBytes; 74 | 75 | aFrames = new Array; 76 | iInc = 0; 77 | 78 | var gifDecoder:GIFDecoder = new GIFDecoder(); 79 | try 80 | { 81 | gifDecoder.read ( gifStream ); 82 | 83 | var lng:int = gifDecoder.getFrameCount(); 84 | 85 | for ( var i:int = 0; i< lng; i++ ) 86 | aFrames[int(i)] = gifDecoder.getFrame(i); 87 | 88 | arrayLng = aFrames.length; 89 | 90 | auto ? play() : gotoAndStop (1); 91 | _loopCount = gifDecoder.getLoopCount(); 92 | 93 | dispatchEvent ( new GIFPlayerEvent ( GIFPlayerEvent.COMPLETE , aFrames[0].bitmapData.rect ) ); 94 | 95 | } catch ( e:ScriptTimeoutError ) 96 | { 97 | dispatchEvent ( new TimeoutEvent ( TimeoutEvent.TIME_OUT ) ); 98 | 99 | } catch ( e:FileTypeError ) 100 | { 101 | dispatchEvent ( new FileTypeEvent ( FileTypeEvent.INVALID ) ); 102 | 103 | } catch ( e:Error ) 104 | { 105 | throw new Error ("An unknown error occured, make sure the GIF file contains at least one frame\nNumber of frames : " + aFrames.length); 106 | } 107 | 108 | } 109 | 110 | private function update ( pEvt:TimerEvent ) :void 111 | { 112 | var frame:GIFFrame = aFrames[ int(iIndex = iInc++ % arrayLng) ]; 113 | var delay:int = frame.delay; 114 | 115 | pEvt.target.delay = ( delay > 0 ) ? delay : 100; 116 | 117 | switch ( frame.dispose ) 118 | { 119 | case 1: 120 | if ( !iIndex ) { 121 | bitmapData = aFrames[ 0 ].bitmapData.clone(); 122 | } 123 | bitmapData.draw ( aFrames[ iIndex ].bitmapData ); 124 | break; 125 | case 2: 126 | bitmapData = aFrames[ iIndex ].bitmapData; 127 | break; 128 | default: 129 | bitmapData = aFrames[ iIndex ].bitmapData; 130 | break; 131 | } 132 | 133 | dispatchEvent ( new FrameEvent ( FrameEvent.FRAME_RENDERED, aFrames[ iIndex ] ) ); 134 | } 135 | 136 | private function concat ( pIndex:int ):int 137 | { 138 | bitmapData.lock(); 139 | for (var i:int = 0; i< pIndex; i++ ) 140 | bitmapData.draw ( aFrames[ i ].bitmapData ); 141 | bitmapData.unlock(); 142 | 143 | return i; 144 | } 145 | 146 | /** 147 | * Load any GIF file 148 | * 149 | * @return void 150 | */ 151 | public function load ( pRequest:URLRequest ):void 152 | { 153 | this.source = pRequest.url; 154 | stop(); 155 | if (byteStreamCache[this.source]) { 156 | var b:ByteArray = new ByteArray(); 157 | ByteArray(byteStreamCache[this.source]).position = 0; 158 | b.writeBytes(byteStreamCache[this.source]); 159 | b.position = 0; 160 | loadBytes(b); 161 | return; 162 | } 163 | urlLoader.load ( pRequest ); 164 | } 165 | 166 | /** 167 | * Load any valid GIF ByteArray 168 | * 169 | * @return void 170 | */ 171 | public function loadBytes ( pBytes:ByteArray ):void 172 | { 173 | readStream ( pBytes ); 174 | } 175 | 176 | /** 177 | * Start playing 178 | * 179 | * @return void 180 | */ 181 | public function play ():void 182 | { 183 | if ( aFrames.length > 0 ) 184 | { 185 | if ( !myTimer.running ) 186 | myTimer.start(); 187 | 188 | } else throw new Error ("Nothing to play"); 189 | } 190 | 191 | /** 192 | * Stop playing 193 | * 194 | * @return void 195 | */ 196 | public function stop ():void 197 | { 198 | if ( myTimer.running ) 199 | myTimer.stop(); 200 | } 201 | 202 | /** 203 | * Returns current frame being played 204 | * 205 | * @return frame number 206 | */ 207 | public function get currentFrame ():int 208 | { 209 | return iIndex+1; 210 | } 211 | 212 | /** 213 | * Returns GIF's total frames 214 | * 215 | * @return number of frames 216 | */ 217 | public function get totalFrames ():int 218 | { 219 | return aFrames.length; 220 | } 221 | 222 | /** 223 | * Returns how many times the GIF file is played 224 | * A loop value of 0 means repeat indefinitiely. 225 | * 226 | * @return loop value 227 | */ 228 | public function get loopCount ():int 229 | { 230 | return _loopCount; 231 | } 232 | 233 | /** 234 | * Returns is the autoPlay value 235 | * 236 | * @return autoPlay value 237 | */ 238 | public function get autoPlay ():Boolean 239 | { 240 | return auto; 241 | } 242 | 243 | /** 244 | * Returns an array of GIFFrame objects 245 | * 246 | * @return aFrames 247 | */ 248 | public function get frames ():Array 249 | { 250 | return aFrames; 251 | } 252 | 253 | /** 254 | * Moves the playhead to the specified frame and stops playing 255 | * 256 | * @return void 257 | */ 258 | public function gotoAndStop (pFrame:int):void 259 | { 260 | if ( pFrame >= 1 && pFrame <= aFrames.length ) 261 | { 262 | if ( pFrame == currentFrame ) return; 263 | iIndex = iInc = int(int(pFrame)-1); 264 | 265 | switch ( aFrames[ pFrame ].dispose ) 266 | { 267 | case 1: 268 | bitmapData = aFrames[ 0 ].bitmapData.clone(); 269 | bitmapData.draw ( aFrames[ concat ( iInc ) ].bitmapData ); 270 | break 271 | case 2: 272 | bitmapData = aFrames[ iInc ].bitmapData; 273 | break; 274 | } 275 | 276 | if ( myTimer.running ) 277 | myTimer.stop(); 278 | 279 | } else throw new RangeError ("Frame out of range, please specify a frame between 1 and " + aFrames.length ); 280 | } 281 | 282 | /** 283 | * Starts playing the GIF at the frame specified as parameter 284 | * 285 | * @return void 286 | */ 287 | public function gotoAndPlay (pFrame:int):void 288 | { 289 | if ( pFrame >= 1 && pFrame <= aFrames.length ) 290 | { 291 | if ( pFrame == currentFrame ) return; 292 | iIndex = iInc = int(int(pFrame)-1); 293 | 294 | switch ( aFrames[ pFrame ].dispose ) 295 | { 296 | case 1: 297 | bitmapData = aFrames[ 0 ].bitmapData.clone(); 298 | bitmapData.draw ( aFrames[ concat ( iInc ) ].bitmapData ); 299 | break 300 | case 2: 301 | bitmapData = aFrames[ iInc ].bitmapData; 302 | break; 303 | } 304 | if ( !myTimer.running ) myTimer.start(); 305 | 306 | } else throw new RangeError ("Frame out of range, please specify a frame between 1 and " + aFrames.length ); 307 | } 308 | 309 | /** 310 | * Retrieves a frame from the GIF file as a BitmapData 311 | * 312 | * @return BitmapData object 313 | */ 314 | public function getFrame ( pFrame:int ):GIFFrame 315 | { 316 | var frame:GIFFrame; 317 | 318 | if ( pFrame >= 1 && pFrame <= aFrames.length ) 319 | frame = aFrames[ pFrame-1 ]; 320 | 321 | else throw new RangeError ("Frame out of range, please specify a frame between 1 and " + aFrames.length ); 322 | 323 | return frame; 324 | } 325 | 326 | /** 327 | * Retrieves the delay for a specific frame 328 | * 329 | * @return int 330 | */ 331 | public function getDelay ( pFrame:int ):int 332 | { 333 | var delay:int; 334 | 335 | if ( pFrame >= 1 && pFrame <= aFrames.length ) 336 | delay = aFrames[ pFrame-1 ].delay; 337 | 338 | else throw new RangeError ("Frame out of range, please specify a frame between 1 and " + aFrames.length ); 339 | 340 | return delay; 341 | } 342 | 343 | /** 344 | * Dispose a GIFPlayer instance 345 | * 346 | * @return int 347 | */ 348 | public function dispose():void 349 | { 350 | stop(); 351 | var lng:int = aFrames.length; 352 | 353 | for ( var i:int = 0; i< lng; i++ ) 354 | aFrames[int(i)].bitmapData.dispose(); 355 | } 356 | } 357 | } 358 | -------------------------------------------------------------------------------- /org/bytearray/gif/encoder/GIFEncoder.as: -------------------------------------------------------------------------------- 1 | /** 2 | * This class lets you encode animated GIF files 3 | * Base class : http://www.java2s.com/Code/Java/2D-Graphics-GUI/AnimatedGifEncoder.htm 4 | * @author Kevin Weiner (original Java version - kweiner@fmsware.com) 5 | * @author Thibault Imbert (AS3 version - bytearray.org) 6 | * @version 0.1 AS3 implementation 7 | */ 8 | 9 | package org.bytearray.gif.encoder 10 | { 11 | import flash.utils.ByteArray; 12 | import flash.display.BitmapData; 13 | import flash.display.Bitmap; 14 | import org.bytearray.gif.encoder.NeuQuant 15 | import flash.net.URLRequestHeader; 16 | import flash.net.URLRequestMethod; 17 | import flash.net.URLRequest; 18 | import flash.net.navigateToURL; 19 | 20 | public class GIFEncoder 21 | { 22 | private var width:int // image size 23 | private var height:int; 24 | private var transparent:* = null; // transparent color if given 25 | private var transIndex:int; // transparent index in color table 26 | private var repeat:int = -1; // no repeat 27 | private var delay:int = 0; // frame delay (hundredths) 28 | private var started:Boolean = false; // ready to output frames 29 | private var out:ByteArray; 30 | private var image:Bitmap; // current frame 31 | private var pixels:ByteArray; // BGR byte array from frame 32 | private var indexedPixels:ByteArray // converted frame indexed to palette 33 | private var colorDepth:int; // number of bit planes 34 | private var colorTab:ByteArray; // RGB palette 35 | private var usedEntry:Array = new Array; // active palette entries 36 | private var palSize:int = 7; // color table size (bits-1) 37 | private var dispose:int = -1; // disposal code (-1 = use default) 38 | private var closeStream:Boolean = false; // close stream when finished 39 | private var firstFrame:Boolean = true; 40 | private var sizeSet:Boolean = false; // if false, get size from first frame 41 | private var sample:int = 10; // default sample interval for quantizer 42 | 43 | /** 44 | * Sets the delay time between each frame, or changes it for subsequent frames 45 | * (applies to last frame added) 46 | * int delay time in milliseconds 47 | * @param ms 48 | */ 49 | 50 | public function setDelay(ms:int):void 51 | { 52 | 53 | delay = Math.round(ms / 10); 54 | 55 | } 56 | 57 | /** 58 | * Sets the GIF frame disposal code for the last added frame and any 59 | * 60 | * subsequent frames. Default is 0 if no transparent color has been set, 61 | * otherwise 2. 62 | * @param code 63 | * int disposal code. 64 | */ 65 | 66 | public function setDispose(code:int):void 67 | { 68 | 69 | if (code >= 0) dispose = code; 70 | 71 | } 72 | 73 | /** 74 | * Sets the number of times the set of GIF frames should be played. Default is 75 | * 1; 0 means play indefinitely. Must be invoked before the first image is 76 | * added. 77 | * 78 | * @param iter 79 | * int number of iterations. 80 | * @return 81 | */ 82 | 83 | public function setRepeat(iter:int):void 84 | { 85 | 86 | if (iter >= 0) repeat = iter; 87 | 88 | } 89 | 90 | /** 91 | * Sets the transparent color for the last added frame and any subsequent 92 | * frames. Since all colors are subject to modification in the quantization 93 | * process, the color in the final palette for each frame closest to the given 94 | * color becomes the transparent color for that frame. May be set to null to 95 | * indicate no transparent color. 96 | * @param 97 | * Color to be treated as transparent on display. 98 | */ 99 | 100 | public function setTransparent(c:Number):void 101 | { 102 | 103 | transparent = c; 104 | 105 | } 106 | 107 | /** 108 | * The addFrame method takes an incoming BitmapData object to create each frames 109 | * @param 110 | * BitmapData object to be treated as a GIF's frame 111 | */ 112 | 113 | public function addFrame(im:BitmapData):Boolean 114 | { 115 | 116 | if ((im == null) || !started || out == null) 117 | 118 | { 119 | throw new Error ("Please call start method before calling addFrame"); 120 | return false; 121 | } 122 | 123 | var ok:Boolean = true; 124 | 125 | try { 126 | 127 | image = new Bitmap ( im ); 128 | if (!sizeSet) setSize(image.width, image.height); 129 | getImagePixels(); // convert to correct format if necessary 130 | analyzePixels(); // build color table & map pixels 131 | 132 | if (firstFrame) 133 | { 134 | writeLSD(); // logical screen descriptior 135 | writePalette(); // global color table 136 | if (repeat >= 0) 137 | { 138 | // use NS app extension to indicate reps 139 | writeNetscapeExt(); 140 | } 141 | } 142 | 143 | writeGraphicCtrlExt(); // write graphic control extension 144 | writeImageDesc(); // image descriptor 145 | if (!firstFrame) writePalette(); // local color table 146 | writePixels(); // encode and write pixel data 147 | firstFrame = false; 148 | } catch (e:Error) { 149 | ok = false; 150 | } 151 | 152 | return ok; 153 | 154 | } 155 | 156 | /** 157 | * Adds final trailer to the GIF stream, if you don't call the finish method 158 | * the GIF stream will not be valid. 159 | */ 160 | 161 | public function finish():Boolean 162 | { 163 | if (!started) return false; 164 | var ok:Boolean = true; 165 | started = false; 166 | try { 167 | out.writeByte(0x3b); // gif trailer 168 | } catch (e:Error) { 169 | ok = false; 170 | } 171 | 172 | return ok; 173 | 174 | } 175 | 176 | /** 177 | * Resets some members so that a new stream can be started. 178 | * This method is actually called by the start method 179 | */ 180 | 181 | private function reset ( ):void 182 | { 183 | 184 | // reset for subsequent use 185 | transIndex = 0; 186 | image = null; 187 | pixels = null; 188 | indexedPixels = null; 189 | colorTab = null; 190 | closeStream = false; 191 | firstFrame = true; 192 | 193 | } 194 | 195 | /** 196 | * * Sets frame rate in frames per second. Equivalent to 197 | * setDelay(1000/fps). 198 | * @param fps 199 | * float frame rate (frames per second) 200 | */ 201 | 202 | public function setFrameRate(fps:Number):void 203 | { 204 | 205 | if (fps != 0xf) delay = Math.round(100/fps); 206 | 207 | } 208 | 209 | /** 210 | * Sets quality of color quantization (conversion of images to the maximum 256 211 | * colors allowed by the GIF specification). Lower values (minimum = 1) 212 | * produce better colors, but slow processing significantly. 10 is the 213 | * default, and produces good color mapping at reasonable speeds. Values 214 | * greater than 20 do not yield significant improvements in speed. 215 | * @param quality 216 | * int greater than 0. 217 | * @return 218 | */ 219 | 220 | public function setQuality(quality:int):void 221 | { 222 | 223 | if (quality < 1) quality = 1; 224 | sample = quality; 225 | 226 | } 227 | 228 | /** 229 | * Sets the GIF frame size. The default size is the size of the first frame 230 | * added if this method is not invoked. 231 | * @param w 232 | * int frame width. 233 | * @param h 234 | * int frame width. 235 | */ 236 | 237 | private function setSize(w:int, h:int):void 238 | { 239 | 240 | if (started && !firstFrame) return; 241 | width = w; 242 | height = h; 243 | if (width < 1)width = 320; 244 | if (height < 1)height = 240; 245 | sizeSet = true 246 | 247 | } 248 | 249 | /** 250 | * Initiates GIF file creation on the given stream. 251 | * @param os 252 | * OutputStream on which GIF images are written. 253 | * @return false if initial write failed. 254 | * 255 | */ 256 | 257 | public function start():Boolean 258 | { 259 | 260 | reset(); 261 | var ok:Boolean = true; 262 | closeStream = false; 263 | out = new ByteArray; 264 | try { 265 | out.writeUTFBytes("GIF89a"); // header 266 | } catch (e:Error) { 267 | ok = false; 268 | } 269 | 270 | return started = ok; 271 | 272 | } 273 | 274 | /** 275 | * Analyzes image colors and creates color map. 276 | */ 277 | 278 | private function analyzePixels():void 279 | { 280 | 281 | var len:int = pixels.length; 282 | var nPix:int = len / 3; 283 | indexedPixels = new ByteArray; 284 | var nq:NeuQuant = new NeuQuant(pixels, len, sample); 285 | // initialize quantizer 286 | colorTab = nq.process(); // create reduced palette 287 | // map image pixels to new palette 288 | var k:int = 0; 289 | for (var j:int = 0; j < nPix; j++) { 290 | var index:int = nq.map(pixels[k++] & 0xff, pixels[k++] & 0xff, pixels[k++] & 0xff); 291 | usedEntry[index] = true; 292 | indexedPixels[j] = index; 293 | } 294 | pixels = null; 295 | colorDepth = 8; 296 | palSize = 7; 297 | // get closest match to transparent color if specified 298 | if (transparent != null) { 299 | transIndex = findClosest(transparent); 300 | } 301 | } 302 | 303 | /** 304 | * Returns index of palette color closest to c 305 | * 306 | */ 307 | 308 | private function findClosest(c:Number):int 309 | { 310 | 311 | if (colorTab == null) return -1; 312 | var r:int = (c & 0xFF0000) >> 16; 313 | var g:int = (c & 0x00FF00) >> 8; 314 | var b:int = (c & 0x0000FF); 315 | var minpos:int = 0; 316 | var dmin:int = 256 * 256 * 256; 317 | var len:int = colorTab.length; 318 | 319 | for (var i:int = 0; i < len;) { 320 | var dr:int = r - (colorTab[i++] & 0xff); 321 | var dg:int = g - (colorTab[i++] & 0xff); 322 | var db:int = b - (colorTab[i] & 0xff); 323 | var d:int = dr * dr + dg * dg + db * db; 324 | var index:int = i / 3; 325 | if (usedEntry[index] && (d < dmin)) { 326 | dmin = d; 327 | minpos = index; 328 | } 329 | i++; 330 | } 331 | return minpos; 332 | 333 | } 334 | 335 | /** 336 | * Extracts image pixels into byte array "pixels 337 | */ 338 | 339 | private function getImagePixels():void 340 | { 341 | 342 | var w:int = image.width; 343 | var h:int = image.height; 344 | pixels = new ByteArray; 345 | 346 | var count:int = 0; 347 | 348 | for ( var i:int = 0; i < h; i++ ) 349 | { 350 | 351 | for (var j:int = 0; j < w; j++ ) 352 | { 353 | 354 | var pixel:Number = image.bitmapData.getPixel( j, i ); 355 | 356 | pixels[count] = (pixel & 0xFF0000) >> 16; 357 | count++; 358 | pixels[count] = (pixel & 0x00FF00) >> 8; 359 | count++; 360 | pixels[count] = (pixel & 0x0000FF); 361 | count++; 362 | 363 | } 364 | 365 | } 366 | 367 | } 368 | 369 | /** 370 | * Writes Graphic Control Extension 371 | */ 372 | 373 | private function writeGraphicCtrlExt():void 374 | { 375 | 376 | out.writeByte(0x21); // extension introducer 377 | out.writeByte(0xf9); // GCE label 378 | out.writeByte(4); // data block size 379 | var transp:int 380 | var disp:int; 381 | if (transparent == null) { 382 | transp = 0; 383 | disp = 0; // dispose = no action 384 | } else { 385 | transp = 1; 386 | disp = 2; // force clear if using transparent color 387 | } 388 | if (dispose >= 0) { 389 | disp = dispose & 7; // user override 390 | } 391 | disp <<= 2; 392 | // packed fields 393 | out.writeByte(0 | // 1:3 reserved 394 | disp | // 4:6 disposal 395 | 0 | // 7 user input - 0 = none 396 | transp); // 8 transparency flag 397 | 398 | WriteShort(delay); // delay x 1/100 sec 399 | out.writeByte(transIndex); // transparent color index 400 | out.writeByte(0); // block terminator 401 | 402 | } 403 | 404 | /** 405 | * Writes Image Descriptor 406 | */ 407 | 408 | private function writeImageDesc():void 409 | { 410 | 411 | out.writeByte(0x2c); // image separator 412 | WriteShort(0); // image position x,y = 0,0 413 | WriteShort(0); 414 | WriteShort(width); // image size 415 | WriteShort(height); 416 | 417 | // packed fields 418 | if (firstFrame) { 419 | // no LCT - GCT is used for first (or only) frame 420 | out.writeByte(0); 421 | } else { 422 | // specify normal LCT 423 | out.writeByte(0x80 | // 1 local color table 1=yes 424 | 0 | // 2 interlace - 0=no 425 | 0 | // 3 sorted - 0=no 426 | 0 | // 4-5 reserved 427 | palSize); // 6-8 size of color table 428 | } 429 | } 430 | 431 | /** 432 | * Writes Logical Screen Descriptor 433 | */ 434 | 435 | private function writeLSD():void 436 | { 437 | 438 | // logical screen size 439 | WriteShort(width); 440 | WriteShort(height); 441 | // packed fields 442 | out.writeByte((0x80 | // 1 : global color table flag = 1 (gct used) 443 | 0x70 | // 2-4 : color resolution = 7 444 | 0x00 | // 5 : gct sort flag = 0 445 | palSize)); // 6-8 : gct size 446 | 447 | out.writeByte(0); // background color index 448 | out.writeByte(0); // pixel aspect ratio - assume 1:1 449 | 450 | } 451 | 452 | /** 453 | * Writes Netscape application extension to define repeat count. 454 | */ 455 | 456 | private function writeNetscapeExt():void 457 | { 458 | 459 | out.writeByte(0x21); // extension introducer 460 | out.writeByte(0xff); // app extension label 461 | out.writeByte(11); // block size 462 | out.writeUTFBytes("NETSCAPE" + "2.0"); // app id + auth code 463 | out.writeByte(3); // sub-block size 464 | out.writeByte(1); // loop sub-block id 465 | WriteShort(repeat); // loop count (extra iterations, 0=repeat forever) 466 | out.writeByte(0); // block terminator 467 | 468 | } 469 | 470 | /** 471 | * Writes color table 472 | */ 473 | 474 | private function writePalette():void 475 | { 476 | 477 | out.writeBytes(colorTab, 0, colorTab.length); 478 | var n:int = (3 * 256) - colorTab.length; 479 | for (var i:int = 0; i < n; i++) out.writeByte(0); 480 | 481 | } 482 | 483 | private function WriteShort (pValue:int):void 484 | { 485 | 486 | out.writeByte( pValue & 0xFF ); 487 | out.writeByte( (pValue >> 8) & 0xFF); 488 | 489 | } 490 | 491 | /** 492 | * Encodes and writes pixel data 493 | */ 494 | 495 | private function writePixels():void 496 | { 497 | 498 | var myencoder:LZWEncoder = new LZWEncoder(width, height, indexedPixels, colorDepth); 499 | myencoder.encode(out); 500 | 501 | } 502 | 503 | /** 504 | * retrieves the GIF stream 505 | */ 506 | public function get stream ( ):ByteArray 507 | { 508 | 509 | return out; 510 | 511 | } 512 | 513 | } 514 | } -------------------------------------------------------------------------------- /org/bytearray/gif/encoder/NeuQuant.as: -------------------------------------------------------------------------------- 1 | /* 2 | * NeuQuant Neural-Net Quantization Algorithm 3 | * ------------------------------------------ 4 | * 5 | * Copyright (c) 1994 Anthony Dekker 6 | * 7 | * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See 8 | * "Kohonen neural networks for optimal colour quantization" in "Network: 9 | * Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of 10 | * the algorithm. 11 | * 12 | * Any party obtaining a copy of these files from the author, directly or 13 | * indirectly, is granted, free of charge, a full and unrestricted irrevocable, 14 | * world-wide, paid up, royalty-free, nonexclusive right and license to deal in 15 | * this software and documentation files (the "Software"), including without 16 | * limitation the rights to use, copy, modify, merge, publish, distribute, 17 | * sublicense, and/or sell copies of the Software, and to permit persons who 18 | * receive copies from any such party to do so, with the only requirement being 19 | * that this copyright notice remain intact. 20 | */ 21 | 22 | /* 23 | * This class handles Neural-Net quantization algorithm 24 | * @author Kevin Weiner (original Java version - kweiner@fmsware.com) 25 | * @author Thibault Imbert (AS3 version - bytearray.org) 26 | * @version 0.1 AS3 implementation 27 | */ 28 | 29 | package org.bytearray.gif.encoder 30 | { 31 | import flash.utils.ByteArray; 32 | 33 | public class NeuQuant 34 | { 35 | private static var netsize:int = 256; /* number of colours used */ 36 | 37 | /* four primes near 500 - assume no image has a length so large */ 38 | /* that it is divisible by all four primes */ 39 | 40 | private static var prime1:int = 499; 41 | private static var prime2:int = 491; 42 | private static var prime3:int = 487; 43 | private static var prime4:int = 503; 44 | private static var minpicturebytes:int = (3 * prime4); 45 | 46 | /* minimum size for input image */ 47 | /* 48 | * Program Skeleton ---------------- [select samplefac in range 1..30] [read 49 | * image from input file] pic = (unsigned char*) malloc(3*width*height); 50 | * initnet(pic,3*width*height,samplefac); learn(); unbiasnet(); [write output 51 | * image header, using writecolourmap(f)] inxbuild(); write output image using 52 | * inxsearch(b,g,r) 53 | */ 54 | 55 | /* 56 | * Network Definitions ------------------- 57 | */ 58 | 59 | private static var maxnetpos:int = (netsize - 1); 60 | private static var netbiasshift:int = 4; /* bias for colour values */ 61 | private static var ncycles:int = 100; /* no. of learning cycles */ 62 | 63 | /* defs for freq and bias */ 64 | private static var intbiasshift:int = 16; /* bias for fractions */ 65 | private static var intbias:int = (1 << intbiasshift); 66 | private static var gammashift:int = 10; /* gamma = 1024 */ 67 | private static var gamma:int = (1 << gammashift); 68 | private static var betashift:int = 10; 69 | private static var beta:int = (intbias >> betashift); /* beta = 1/1024 */ 70 | private static var betagamma:int = (intbias << (gammashift - betashift)); 71 | 72 | /* defs for decreasing radius factor */ 73 | private static var initrad:int = (netsize >> 3); /* 74 | * for 256 cols, radius 75 | * starts 76 | */ 77 | 78 | private static var radiusbiasshift:int = 6; /* at 32.0 biased by 6 bits */ 79 | private static var radiusbias:int = (1 << radiusbiasshift); 80 | private static var initradius:int = (initrad * radiusbias); /* 81 | * and 82 | * decreases 83 | * by a 84 | */ 85 | 86 | private static var radiusdec:int = 30; /* factor of 1/30 each cycle */ 87 | 88 | /* defs for decreasing alpha factor */ 89 | private static var alphabiasshift:int = 10; /* alpha starts at 1.0 */ 90 | private static var initalpha:int = (1 << alphabiasshift); 91 | private var alphadec:int /* biased by 10 bits */ 92 | 93 | /* radbias and alpharadbias used for radpower calculation */ 94 | private static var radbiasshift:int = 8; 95 | private static var radbias:int = (1 << radbiasshift); 96 | private static var alpharadbshift:int = (alphabiasshift + radbiasshift); 97 | 98 | private static var alpharadbias:int = (1 << alpharadbshift); 99 | 100 | /* 101 | * Types and Global Variables -------------------------- 102 | */ 103 | 104 | private var thepicture:ByteArray/* the input image itself */ 105 | private var lengthcount:int; /* lengthcount = H*W*3 */ 106 | private var samplefac:int; /* sampling factor 1..30 */ 107 | 108 | // typedef int pixel[4]; /* BGRc */ 109 | private var network:Array; /* the network itself - [netsize][4] */ 110 | protected var netindex:Array = new Array(); 111 | 112 | /* for network lookup - really 256 */ 113 | private var bias:Array = new Array(); 114 | 115 | /* bias and freq arrays for learning */ 116 | private var freq:Array = new Array(); 117 | private var radpower:Array = new Array(); 118 | 119 | public function NeuQuant(thepic:ByteArray, len:int, sample:int) 120 | { 121 | 122 | var i:int; 123 | var p:Array; 124 | 125 | thepicture = thepic; 126 | lengthcount = len; 127 | samplefac = sample; 128 | 129 | network = new Array(netsize); 130 | 131 | for (i = 0; i < netsize; i++) 132 | { 133 | 134 | network[i] = new Array(4); 135 | p = network[i]; 136 | p[0] = p[1] = p[2] = (i << (netbiasshift + 8)) / netsize; 137 | freq[i] = intbias / netsize; /* 1/netsize */ 138 | bias[i] = 0; 139 | } 140 | 141 | } 142 | 143 | private function colorMap():ByteArray 144 | { 145 | 146 | var map:ByteArray = new ByteArray; 147 | var index:Array = new Array(netsize); 148 | for (var i:int = 0; i < netsize; i++) 149 | index[network[i][3]] = i; 150 | var k:int = 0; 151 | for (var l:int = 0; l < netsize; l++) { 152 | var j:int = index[l]; 153 | map[k++] = (network[j][0]); 154 | map[k++] = (network[j][1]); 155 | map[k++] = (network[j][2]); 156 | } 157 | return map; 158 | 159 | } 160 | 161 | /* 162 | * Insertion sort of network and building of netindex[0..255] (to do after 163 | * unbias) 164 | * ------------------------------------------------------------------------------- 165 | */ 166 | 167 | private function inxbuild():void 168 | { 169 | 170 | var i:int; 171 | var j:int; 172 | var smallpos:int; 173 | var smallval:int; 174 | var p:Array; 175 | var q:Array; 176 | var previouscol:int 177 | var startpos:int 178 | 179 | previouscol = 0; 180 | startpos = 0; 181 | for (i = 0; i < netsize; i++) 182 | { 183 | 184 | p = network[i]; 185 | smallpos = i; 186 | smallval = p[1]; /* index on g */ 187 | /* find smallest in i..netsize-1 */ 188 | for (j = i + 1; j < netsize; j++) 189 | { 190 | q = network[j]; 191 | if (q[1] < smallval) 192 | { /* index on g */ 193 | 194 | smallpos = j; 195 | smallval = q[1]; /* index on g */ 196 | } 197 | } 198 | 199 | q = network[smallpos]; 200 | /* swap p (i) and q (smallpos) entries */ 201 | 202 | if (i != smallpos) 203 | { 204 | 205 | j = q[0]; 206 | q[0] = p[0]; 207 | p[0] = j; 208 | j = q[1]; 209 | q[1] = p[1]; 210 | p[1] = j; 211 | j = q[2]; 212 | q[2] = p[2]; 213 | p[2] = j; 214 | j = q[3]; 215 | q[3] = p[3]; 216 | p[3] = j; 217 | 218 | } 219 | 220 | /* smallval entry is now in position i */ 221 | 222 | if (smallval != previouscol) 223 | 224 | { 225 | 226 | netindex[previouscol] = (startpos + i) >> 1; 227 | 228 | for (j = previouscol + 1; j < smallval; j++) netindex[j] = i; 229 | 230 | previouscol = smallval; 231 | startpos = i; 232 | 233 | } 234 | 235 | } 236 | 237 | netindex[previouscol] = (startpos + maxnetpos) >> 1; 238 | for (j = previouscol + 1; j < 256; j++) netindex[j] = maxnetpos; /* really 256 */ 239 | 240 | } 241 | 242 | /* 243 | * Main Learning Loop ------------------ 244 | */ 245 | 246 | private function learn():void 247 | 248 | { 249 | 250 | var i:int; 251 | var j:int; 252 | var b:int; 253 | var g:int 254 | var r:int; 255 | var radius:int; 256 | var rad:int; 257 | var alpha:int; 258 | var step:int; 259 | var delta:int; 260 | var samplepixels:int; 261 | var p:ByteArray; 262 | var pix:int; 263 | var lim:int; 264 | 265 | if (lengthcount < minpicturebytes) samplefac = 1; 266 | 267 | alphadec = 30 + ((samplefac - 1) / 3); 268 | p = thepicture; 269 | pix = 0; 270 | lim = lengthcount; 271 | samplepixels = lengthcount / (3 * samplefac); 272 | delta = samplepixels / ncycles; 273 | alpha = initalpha; 274 | radius = initradius; 275 | 276 | rad = radius >> radiusbiasshift; 277 | if (rad <= 1) rad = 0; 278 | 279 | for (i = 0; i < rad; i++) radpower[i] = alpha * (((rad * rad - i * i) * radbias) / (rad * rad)); 280 | 281 | 282 | if (lengthcount < minpicturebytes) step = 3; 283 | 284 | else if ((lengthcount % prime1) != 0) step = 3 * prime1; 285 | 286 | else 287 | 288 | { 289 | 290 | if ((lengthcount % prime2) != 0) step = 3 * prime2; 291 | 292 | else 293 | 294 | { 295 | 296 | if ((lengthcount % prime3) != 0) step = 3 * prime3; 297 | 298 | else step = 3 * prime4; 299 | 300 | } 301 | 302 | } 303 | 304 | i = 0; 305 | 306 | while (i < samplepixels) 307 | 308 | { 309 | 310 | b = (p[pix + 0] & 0xff) << netbiasshift; 311 | g = (p[pix + 1] & 0xff) << netbiasshift; 312 | r = (p[pix + 2] & 0xff) << netbiasshift; 313 | j = contest(b, g, r); 314 | 315 | altersingle(alpha, j, b, g, r); 316 | 317 | if (rad != 0) alterneigh(rad, j, b, g, r); /* alter neighbours */ 318 | 319 | pix += step; 320 | 321 | if (pix >= lim) pix -= lengthcount; 322 | 323 | i++; 324 | 325 | if (delta == 0) delta = 1; 326 | 327 | if (i % delta == 0) 328 | 329 | { 330 | 331 | alpha -= alpha / alphadec; 332 | radius -= radius / radiusdec; 333 | rad = radius >> radiusbiasshift; 334 | 335 | if (rad <= 1) rad = 0; 336 | 337 | for (j = 0; j < rad; j++) radpower[j] = alpha * (((rad * rad - j * j) * radbias) / (rad * rad)); 338 | 339 | } 340 | 341 | } 342 | 343 | } 344 | 345 | /* 346 | ** Search for BGR values 0..255 (after net is unbiased) and return colour 347 | * index 348 | * ---------------------------------------------------------------------------- 349 | */ 350 | 351 | public function map(b:int, g:int, r:int):int 352 | 353 | { 354 | 355 | var i:int; 356 | var j:int; 357 | var dist:int 358 | var a:int; 359 | var bestd:int; 360 | var p:Array; 361 | var best:int; 362 | 363 | bestd = 1000; /* biggest possible dist is 256*3 */ 364 | best = -1; 365 | i = netindex[g]; /* index on g */ 366 | j = i - 1; /* start at netindex[g] and work outwards */ 367 | 368 | while ((i < netsize) || (j >= 0)) 369 | 370 | { 371 | 372 | if (i < netsize) 373 | 374 | { 375 | 376 | p = network[i]; 377 | 378 | dist = p[1] - g; /* inx key */ 379 | 380 | if (dist >= bestd) i = netsize; /* stop iter */ 381 | 382 | else 383 | 384 | { 385 | 386 | i++; 387 | 388 | if (dist < 0) dist = -dist; 389 | 390 | a = p[0] - b; 391 | 392 | if (a < 0) a = -a; 393 | 394 | dist += a; 395 | 396 | if (dist < bestd) 397 | 398 | { 399 | 400 | a = p[2] - r; 401 | 402 | if (a < 0) a = -a; 403 | 404 | dist += a; 405 | 406 | if (dist < bestd) 407 | 408 | { 409 | 410 | bestd = dist; 411 | best = p[3]; 412 | 413 | } 414 | 415 | } 416 | 417 | } 418 | 419 | } 420 | 421 | if (j >= 0) 422 | { 423 | 424 | p = network[j]; 425 | 426 | dist = g - p[1]; /* inx key - reverse dif */ 427 | 428 | if (dist >= bestd) j = -1; /* stop iter */ 429 | 430 | else 431 | { 432 | 433 | j--; 434 | if (dist < 0) dist = -dist; 435 | a = p[0] - b; 436 | if (a < 0) a = -a; 437 | dist += a; 438 | 439 | if (dist < bestd) 440 | 441 | { 442 | 443 | a = p[2] - r; 444 | if (a < 0)a = -a; 445 | dist += a; 446 | if (dist < bestd) 447 | { 448 | bestd = dist; 449 | best = p[3]; 450 | } 451 | 452 | } 453 | 454 | } 455 | 456 | } 457 | 458 | } 459 | 460 | return (best); 461 | 462 | } 463 | 464 | public function process():ByteArray 465 | { 466 | 467 | learn(); 468 | unbiasnet(); 469 | inxbuild(); 470 | return colorMap(); 471 | 472 | } 473 | 474 | /* 475 | * Unbias network to give byte values 0..255 and record position i to prepare 476 | * for sort 477 | * ----------------------------------------------------------------------------------- 478 | */ 479 | 480 | private function unbiasnet():void 481 | 482 | { 483 | 484 | var i:int; 485 | var j:int; 486 | 487 | for (i = 0; i < netsize; i++) 488 | { 489 | network[i][0] >>= netbiasshift; 490 | network[i][1] >>= netbiasshift; 491 | network[i][2] >>= netbiasshift; 492 | network[i][3] = i; /* record colour no */ 493 | } 494 | 495 | } 496 | 497 | /* 498 | * Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in 499 | * radpower[|i-j|] 500 | * --------------------------------------------------------------------------------- 501 | */ 502 | 503 | private function alterneigh(rad:int, i:int, b:int, g:int, r:int):void 504 | 505 | { 506 | 507 | var j:int; 508 | var k:int; 509 | var lo:int; 510 | var hi:int; 511 | var a:int; 512 | var m:int; 513 | 514 | var p:Array; 515 | 516 | lo = i - rad; 517 | if (lo < -1) lo = -1; 518 | 519 | hi = i + rad; 520 | 521 | if (hi > netsize) hi = netsize; 522 | 523 | j = i + 1; 524 | k = i - 1; 525 | m = 1; 526 | 527 | while ((j < hi) || (k > lo)) 528 | 529 | { 530 | 531 | a = radpower[m++]; 532 | 533 | if (j < hi) 534 | 535 | { 536 | 537 | p = network[j++]; 538 | 539 | try { 540 | 541 | p[0] -= (a * (p[0] - b)) / alpharadbias; 542 | p[1] -= (a * (p[1] - g)) / alpharadbias; 543 | p[2] -= (a * (p[2] - r)) / alpharadbias; 544 | 545 | } catch (e:Error) {} // prevents 1.3 miscompilation 546 | 547 | } 548 | 549 | if (k > lo) 550 | 551 | { 552 | 553 | p = network[k--]; 554 | 555 | try 556 | { 557 | 558 | p[0] -= (a * (p[0] - b)) / alpharadbias; 559 | p[1] -= (a * (p[1] - g)) / alpharadbias; 560 | p[2] -= (a * (p[2] - r)) / alpharadbias; 561 | 562 | } catch (e:Error) {} 563 | 564 | } 565 | 566 | } 567 | 568 | } 569 | 570 | /* 571 | * Move neuron i towards biased (b,g,r) by factor alpha 572 | * ---------------------------------------------------- 573 | */ 574 | 575 | private function altersingle(alpha:int, i:int, b:int, g:int, r:int):void 576 | { 577 | 578 | /* alter hit neuron */ 579 | var n:Array = network[i]; 580 | n[0] -= (alpha * (n[0] - b)) / initalpha; 581 | n[1] -= (alpha * (n[1] - g)) / initalpha; 582 | n[2] -= (alpha * (n[2] - r)) / initalpha; 583 | 584 | } 585 | 586 | /* 587 | * Search for biased BGR values ---------------------------- 588 | */ 589 | 590 | private function contest(b:int, g:int, r:int):int 591 | { 592 | 593 | /* finds closest neuron (min dist) and updates freq */ 594 | /* finds best neuron (min dist-bias) and returns position */ 595 | /* for frequently chosen neurons, freq[i] is high and bias[i] is negative */ 596 | /* bias[i] = gamma*((1/netsize)-freq[i]) */ 597 | 598 | var i:int; 599 | var dist:int; 600 | var a:int; 601 | var biasdist:int; 602 | var betafreq:int; 603 | var bestpos:int; 604 | var bestbiaspos:int; 605 | var bestd:int; 606 | var bestbiasd:int; 607 | var n:Array; 608 | 609 | bestd = ~(1 << 31); 610 | bestbiasd = bestd; 611 | bestpos = -1; 612 | bestbiaspos = bestpos; 613 | 614 | for (i = 0; i < netsize; i++) 615 | 616 | { 617 | 618 | n = network[i]; 619 | dist = n[0] - b; 620 | 621 | if (dist < 0) dist = -dist; 622 | 623 | a = n[1] - g; 624 | 625 | if (a < 0) a = -a; 626 | 627 | dist += a; 628 | 629 | a = n[2] - r; 630 | 631 | if (a < 0) a = -a; 632 | 633 | dist += a; 634 | 635 | if (dist < bestd) 636 | 637 | { 638 | 639 | bestd = dist; 640 | bestpos = i; 641 | 642 | } 643 | 644 | biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift)); 645 | 646 | if (biasdist < bestbiasd) 647 | 648 | { 649 | 650 | bestbiasd = biasdist; 651 | bestbiaspos = i; 652 | 653 | } 654 | 655 | betafreq = (freq[i] >> betashift); 656 | freq[i] -= betafreq; 657 | bias[i] += (betafreq << gammashift); 658 | 659 | } 660 | 661 | freq[bestpos] += beta; 662 | bias[bestpos] -= betagamma; 663 | return (bestbiaspos); 664 | 665 | } 666 | 667 | } 668 | 669 | } -------------------------------------------------------------------------------- /org/bytearray/gif/decoder/GIFDecoder.as: -------------------------------------------------------------------------------- 1 | /** 2 | * This class lets you decode animated GIF files, and show animated GIF's in the Flash player 3 | * Base Class : http://www.java2s.com/Code/Java/2D-Graphics-GUI/GiffileEncoder.htm 4 | * @author Kevin Weiner (original Java version - kweiner@fmsware.com) 5 | * @author Thibault Imbert (AS3 version - bytearray.org) 6 | * @version 0.1 AS3 implementation 7 | */ 8 | 9 | package org.bytearray.gif.decoder 10 | { 11 | import flash.display.Bitmap; 12 | import flash.display.BitmapData; 13 | import flash.geom.Rectangle; 14 | import flash.utils.ByteArray; 15 | import org.bytearray.gif.errors.FileTypeError; 16 | import org.bytearray.gif.frames.GIFFrame; 17 | 18 | public class GIFDecoder 19 | { 20 | /** 21 | * File read status: No errors. 22 | */ 23 | private static var STATUS_OK:int = 0; 24 | 25 | /** 26 | * File read status: Error decoding file (may be partially decoded) 27 | */ 28 | private static var STATUS_FORMAT_ERROR:int = 1; 29 | 30 | /** 31 | * File read status: Unable to open source. 32 | */ 33 | private static var STATUS_OPEN_ERROR:int = 2; 34 | 35 | private static var frameRect:Rectangle = new Rectangle; 36 | 37 | private var inStream:ByteArray; 38 | private var status:int; 39 | 40 | // full image width 41 | private var width:int; 42 | // full image height 43 | private var height:int; 44 | // global color table used 45 | private var gctFlag:Boolean; 46 | // size of global color table 47 | private var gctSize:int; 48 | // iterations; 0 = repeat forever 49 | private var loopCount:int = 1; 50 | 51 | // global color table 52 | private var gct:Array; 53 | // local color table 54 | private var lct:Array; 55 | // active color table 56 | private var act:Array; 57 | 58 | // background color index 59 | private var bgIndex:int; 60 | // background color 61 | private var bgColor:int; 62 | // previous bg color 63 | private var lastBgColor:int; 64 | // pixel aspect ratio 65 | private var pixelAspect:int; 66 | 67 | private var lctFlag:Boolean // local color table flag 68 | // interlace flag 69 | private var interlace:Boolean; 70 | // local color table size 71 | private var lctSize:int; 72 | 73 | private var ix:int; 74 | private var iy:int; 75 | private var iw:int; 76 | // current image rectangle 77 | private var ih:int; 78 | // last image rect 79 | private var lastRect:Rectangle; 80 | // current frame 81 | private var image:BitmapData; 82 | private var bitmap:BitmapData; 83 | // previous frame 84 | private var lastImage:BitmapData; 85 | // current data block 86 | private var block:ByteArray; 87 | // block size 88 | private var blockSize:int = 0; 89 | 90 | // last graphic control extension info 91 | private var dispose:int= 0; 92 | // 0=no action; 1=leave in place; 2=restore to bg; 3=restore to prev 93 | private var lastDispose:int = 0; 94 | // use transparent color 95 | private var transparency:Boolean = false; 96 | // delay in milliseconds 97 | private var delay:int = 0; 98 | // transparent color index 99 | private var transIndex:int; 100 | 101 | // max decoder pixel stack size 102 | private static var MaxStackSize:int = 4096; 103 | 104 | // LZW decoder working arrays 105 | private var prefix:Array 106 | private var suffix:Array; 107 | private var pixelStack:Array; 108 | private var pixels:Array; 109 | 110 | // frames read from current file 111 | private var frames:Array 112 | private var frameCount:int 113 | 114 | public function GIFDecoder ( ) 115 | { 116 | block = new ByteArray; 117 | } 118 | 119 | public function get disposeValue():int 120 | { 121 | return dispose; 122 | } 123 | 124 | /** 125 | * Gets display duration for specified frame. 126 | * 127 | * @param n int index of frame 128 | * @return delay in milliseconds 129 | */ 130 | public function getDelay(n:int):int 131 | { 132 | delay = -1; 133 | if ((n >= 0) && (n < frameCount)) 134 | { 135 | delay = frames[n].delay; 136 | } 137 | return delay; 138 | } 139 | 140 | /** 141 | * Gets the number of frames read from file. 142 | * @return frame count 143 | */ 144 | public function getFrameCount():int 145 | { 146 | return frameCount; 147 | } 148 | 149 | /** 150 | * Gets the first (or only) image read. 151 | * 152 | * @return BitmapData containing first frame, or null if none. 153 | */ 154 | public function getImage():GIFFrame 155 | { 156 | return getFrame(0); 157 | } 158 | 159 | /** 160 | * Gets the "Netscape" iteration count, if any. 161 | * A count of 0 means repeat indefinitiely. 162 | * 163 | * @return iteration count if one was specified, else 1. 164 | */ 165 | public function getLoopCount():int 166 | { 167 | return loopCount; 168 | } 169 | 170 | /** 171 | * Creates new frame image from current data (and previous 172 | * frames as specified by their disposition codes). 173 | */ 174 | private function getPixels( bitmap:BitmapData ):Array 175 | { 176 | var pixels:Array = new Array ( 4 * image.width * image.height ); 177 | var count:int = 0; 178 | var lngWidth:int = image.width; 179 | var lngHeight:int = image.height; 180 | var color:int; 181 | 182 | for (var th:int = 0; th < lngHeight; th++) 183 | { 184 | for (var tw:int = 0; tw < lngWidth; tw++) 185 | { 186 | color = bitmap.getPixel (th, tw); 187 | 188 | pixels[count++] = (color & 0xFF0000) >> 16; 189 | pixels[count++] = (color & 0x00FF00) >> 8; 190 | pixels[count++] = (color & 0x0000FF); 191 | } 192 | } 193 | return pixels; 194 | } 195 | 196 | private function setPixels( pixels:Array ):void 197 | { 198 | var count:int = 0; 199 | var color:int; 200 | pixels.position = 0; 201 | 202 | var lngWidth:int = image.width; 203 | var lngHeight:int = image.height; 204 | bitmap.lock(); 205 | 206 | for (var th:int = 0; th < lngHeight; th++) 207 | { 208 | for (var tw:int = 0; tw < lngWidth; tw++) 209 | { 210 | color = pixels[int(count++)]; 211 | bitmap.setPixel32 ( tw, th, color ); 212 | } 213 | } 214 | bitmap.unlock(); 215 | } 216 | 217 | private function transferPixels():void 218 | { 219 | // expose destination image's pixels as int array 220 | var dest:Array = getPixels( bitmap ); 221 | // fill in starting image contents based on last image's dispose code 222 | if (lastDispose > 0) 223 | { 224 | if (lastDispose == 3) 225 | { 226 | // use image before last 227 | var n:int = frameCount - 2; 228 | lastImage = n > 0 ? getFrame(n - 1).bitmapData : null; 229 | 230 | } 231 | 232 | if (lastImage != null) 233 | { 234 | var prev:Array = getPixels( lastImage ); 235 | dest = prev.slice(); 236 | // copy pixels 237 | if (lastDispose == 2) 238 | { 239 | // fill last image rect area with background color 240 | var c:Number; 241 | // assume background is transparent 242 | c = transparency ? 0x00000000 : lastBgColor; 243 | // use given background color 244 | image.fillRect( lastRect, c ); 245 | } 246 | } 247 | } 248 | 249 | // copy each source line to the appropriate place in the destination 250 | var pass:int = 1; 251 | var inc:int = 8; 252 | var iline:int = 0; 253 | for (var i:int = 0; i < ih; i++) 254 | { 255 | var line:int = i; 256 | if (interlace) 257 | { 258 | if (iline >= ih) 259 | { 260 | pass++; 261 | switch (pass) 262 | { 263 | case 2 : 264 | iline = 4; 265 | break; 266 | case 3 : 267 | iline = 2; 268 | inc = 4; 269 | break; 270 | case 4 : 271 | iline = 1; 272 | inc = 2; 273 | break; 274 | } 275 | } 276 | line = iline; 277 | iline += inc; 278 | } 279 | line += iy; 280 | if (line < height) 281 | { 282 | var k:int = line * width; 283 | var dx:int = k + ix; // start of line in dest 284 | var dlim:int = dx + iw; // end of dest line 285 | if ((k + width) < dlim) 286 | { 287 | dlim = k + width; // past dest edge 288 | } 289 | var sx:int = i * iw; // start of line in source 290 | var index:int; 291 | var tmp:int; 292 | while (dx < dlim) 293 | { 294 | // map color and insert in destination 295 | index = (pixels[sx++]) & 0xff; 296 | tmp = act[index]; 297 | if (tmp != 0) 298 | { 299 | dest[dx] = tmp; 300 | } 301 | dx++; 302 | } 303 | } 304 | } 305 | setPixels( dest ); 306 | } 307 | 308 | /** 309 | * Gets the image contents of frame n. 310 | * 311 | * @return BufferedImage representation of frame, or null if n is invalid. 312 | */ 313 | public function getFrame(n:int):GIFFrame 314 | { 315 | var im:GIFFrame = null; 316 | 317 | if ((n >= 0) && (n < frameCount)) 318 | 319 | { 320 | im =frames[n]; 321 | 322 | } else throw new RangeError ("Wrong frame number passed"); 323 | 324 | return im; 325 | } 326 | 327 | /** 328 | * Gets image size. 329 | * 330 | * @return GIF image dimensions 331 | */ 332 | public function getFrameSize():Rectangle 333 | { 334 | var rect:Rectangle = GIFDecoder.frameRect; 335 | 336 | rect.x = rect.y = 0; rect.width = width; rect.height = height; 337 | 338 | return rect; 339 | } 340 | 341 | /** 342 | * Reads GIF image from stream 343 | * 344 | * @param BufferedInputStream containing GIF file. 345 | * @return read status code (0 = no errors) 346 | */ 347 | public function read( inStream:ByteArray ):int 348 | { 349 | init(); 350 | if ( inStream != null) 351 | { 352 | this.inStream = inStream; 353 | readHeader(); 354 | 355 | if (!hasError()) 356 | { 357 | readContents(); 358 | 359 | if (frameCount < 0) status = STATUS_FORMAT_ERROR; 360 | } 361 | } 362 | else 363 | { 364 | status = STATUS_OPEN_ERROR; 365 | } 366 | return status; 367 | } 368 | 369 | /** 370 | * Decodes LZW image data into pixel array. 371 | * Adapted from John Cristy's ImageMagick. 372 | */ 373 | private function decodeImageData():void 374 | { 375 | var NullCode:int = -1; 376 | var npix:int = iw * ih; 377 | var available:int; 378 | var clear:int; 379 | var code_mask:int; 380 | var code_size:int; 381 | var end_of_information:int; 382 | var in_code:int; 383 | var old_code:int; 384 | var bits:int; 385 | var code:int; 386 | var count:int; 387 | var i:int; 388 | var datum:int; 389 | var data_size:int; 390 | var first:int; 391 | var top:int; 392 | var bi:int; 393 | var pi:int; 394 | 395 | if ((pixels == null) || (pixels.length < npix)) 396 | { 397 | pixels = new Array ( npix ); // allocate new pixel array 398 | } 399 | if (prefix == null) prefix = new Array ( MaxStackSize ); 400 | if (suffix == null) suffix = new Array ( MaxStackSize ); 401 | if (pixelStack == null) pixelStack = new Array ( MaxStackSize + 1 ); 402 | 403 | // Initialize GIF data stream decoder. 404 | 405 | data_size = readSingleByte(); 406 | clear = 1 << data_size; 407 | end_of_information = clear + 1; 408 | available = clear + 2; 409 | old_code = NullCode; 410 | code_size = data_size + 1; 411 | code_mask = (1 << code_size) - 1; 412 | for (code = 0; code < clear; code++) 413 | { 414 | prefix[int(code)] = 0; 415 | suffix[int(code)] = code; 416 | } 417 | 418 | // Decode GIF pixel stream. 419 | datum = bits = count = first = top = pi = bi = 0; 420 | 421 | for (i = 0; i < npix;) 422 | { 423 | if (top == 0) 424 | { 425 | if (bits < code_size) 426 | { 427 | // Load bytes until there are enough bits for a code. 428 | if (count == 0) 429 | { 430 | // Read a new data block. 431 | count = readBlock(); 432 | if (count <= 0) 433 | break; 434 | bi = 0; 435 | } 436 | datum += (int((block[int(bi)])) & 0xff) << bits; 437 | bits += 8; 438 | bi++; 439 | count--; 440 | continue; 441 | } 442 | 443 | // Get the next code. 444 | code = datum & code_mask; 445 | datum >>= code_size; 446 | bits -= code_size; 447 | // Interpret the code 448 | if ((code > available) || (code == end_of_information)) 449 | break; 450 | if (code == clear) 451 | { 452 | // Reset decoder. 453 | code_size = data_size + 1; 454 | code_mask = (1 << code_size) - 1; 455 | available = clear + 2; 456 | old_code = NullCode; 457 | continue; 458 | } 459 | if (old_code == NullCode) 460 | { 461 | pixelStack[int(top++)] = suffix[int(code)]; 462 | old_code = code; 463 | first = code; 464 | continue; 465 | } 466 | in_code = code; 467 | if (code == available) 468 | { 469 | pixelStack[int(top++)] = first; 470 | code = old_code; 471 | } 472 | while (code > clear) 473 | { 474 | pixelStack[int(top++)] = suffix[int(code)]; 475 | code = prefix[int(code)]; 476 | } 477 | first = (suffix[int(code)]) & 0xff; 478 | 479 | // Add a new string to the string table, 480 | 481 | if (available >= MaxStackSize) break; 482 | pixelStack[int(top++)] = first; 483 | prefix[int(available)] = old_code; 484 | suffix[int(available)] = first; 485 | available++; 486 | if (((available & code_mask) == 0) 487 | && (available < MaxStackSize)) 488 | { 489 | code_size++; 490 | code_mask += available; 491 | } 492 | old_code = in_code; 493 | } 494 | 495 | // Pop a pixel off the pixel stack. 496 | 497 | top--; 498 | pixels[int(pi++)] = pixelStack[int(top)]; 499 | i++; 500 | } 501 | 502 | for (i = pi; i < npix; i++) 503 | { 504 | pixels[int(i)] = 0; // clear missing pixels 505 | } 506 | 507 | } 508 | 509 | /** 510 | * Returns true if an error was encountered during reading/decoding 511 | */ 512 | private function hasError():Boolean 513 | { 514 | return status != STATUS_OK; 515 | } 516 | 517 | /** 518 | * Initializes or re-initializes reader 519 | */ 520 | private function init():void 521 | { 522 | status = STATUS_OK; 523 | frameCount = 0; 524 | frames = new Array; 525 | gct = null; 526 | lct = null; 527 | } 528 | 529 | /** 530 | * Reads a single byte from the input stream. 531 | */ 532 | private function readSingleByte():int 533 | { 534 | var curByte:int = 0; 535 | try 536 | { 537 | curByte = inStream.readUnsignedByte(); 538 | } 539 | catch (e:Error) 540 | { 541 | status = STATUS_FORMAT_ERROR; 542 | } 543 | return curByte; 544 | } 545 | 546 | /** 547 | * Reads next variable length block from input. 548 | * 549 | * @return number of bytes stored in "buffer" 550 | */ 551 | private function readBlock():int 552 | { 553 | blockSize = readSingleByte(); 554 | var n:int = 0; 555 | if (blockSize > 0) 556 | { 557 | try 558 | { 559 | var count:int = 0; 560 | while (n < blockSize) 561 | { 562 | 563 | inStream.readBytes(block, n, blockSize - n); 564 | if ( (blockSize - n) == -1) 565 | break; 566 | n += (blockSize - n); 567 | } 568 | } 569 | catch (e:Error) 570 | { 571 | } 572 | 573 | if (n < blockSize) 574 | { 575 | status = STATUS_FORMAT_ERROR; 576 | } 577 | } 578 | return n; 579 | } 580 | 581 | /** 582 | * Reads color table as 256 RGB integer values 583 | * 584 | * @param ncolors int number of colors to read 585 | * @return int array containing 256 colors (packed ARGB with full alpha) 586 | */ 587 | private function readColorTable(ncolors:int):Array 588 | { 589 | var nbytes:int = 3 * ncolors; 590 | var tab:Array = null; 591 | var c:ByteArray = new ByteArray; 592 | var n:int = 0; 593 | try 594 | { 595 | inStream.readBytes(c, 0, nbytes ); 596 | n = nbytes; 597 | } 598 | catch (e:Error) 599 | { 600 | } 601 | if (n < nbytes) 602 | { 603 | status = STATUS_FORMAT_ERROR; 604 | } 605 | else 606 | { 607 | tab = new Array(256); // max size to avoid bounds checks 608 | var i:int = 0; 609 | var j:int = 0; 610 | while (i < ncolors) 611 | { 612 | var r:int = (c[j++]) & 0xff; 613 | var g:int = (c[j++]) & 0xff; 614 | var b:int = (c[j++]) & 0xff; 615 | tab[i++] = ( 0xff000000 | (r << 16) | (g << 8) | b ); 616 | } 617 | } 618 | return tab; 619 | } 620 | 621 | /** 622 | * Main file parser. Reads GIF content blocks. 623 | */ 624 | private function readContents():void 625 | { 626 | // read GIF file content blocks 627 | var done:Boolean = false; 628 | 629 | while (!(done || hasError())) 630 | { 631 | 632 | var code:int = readSingleByte(); 633 | 634 | switch (code) 635 | { 636 | 637 | case 0x2C : // image separator 638 | readImage(); 639 | break; 640 | 641 | case 0x21 : // extension 642 | code = readSingleByte(); 643 | switch (code) 644 | { 645 | case 0xf9 : // graphics control extension 646 | readGraphicControlExt(); 647 | break; 648 | 649 | case 0xff : // application extension 650 | readBlock(); 651 | var app:String = ""; 652 | for (var i:int = 0; i < 11; i++) 653 | { 654 | app += block[int(i)]; 655 | } 656 | if (app == "NETSCAPE2.0") 657 | { 658 | readNetscapeExt(); 659 | } 660 | else 661 | skip(); // don't care 662 | break; 663 | 664 | default : // uninteresting extension 665 | skip(); 666 | break; 667 | } 668 | break; 669 | 670 | case 0x3b : // terminator 671 | done = true; 672 | break; 673 | 674 | case 0x00 : // bad byte, but keep going and see what happens 675 | break; 676 | 677 | default : 678 | status = STATUS_FORMAT_ERROR; 679 | break; 680 | } 681 | } 682 | } 683 | 684 | /** 685 | * Reads Graphics Control Extension values 686 | */ 687 | private function readGraphicControlExt():void 688 | { 689 | readSingleByte(); // block size 690 | var packed:int = readSingleByte(); // packed fields 691 | dispose = (packed & 0x1c) >> 2; // disposal method 692 | if (dispose == 0) 693 | { 694 | dispose = 1; // elect to keep old image if discretionary 695 | } 696 | transparency = (packed & 1) != 0; 697 | delay = readShort() * 10; // delay in milliseconds 698 | transIndex = readSingleByte(); // transparent color index 699 | readSingleByte(); // block terminator 700 | } 701 | 702 | /** 703 | * Reads GIF file header information. 704 | */ 705 | private function readHeader():void 706 | { 707 | var id:String = ""; 708 | for (var i:int = 0; i < 6; i++) 709 | { 710 | id += String.fromCharCode (readSingleByte()); 711 | 712 | } 713 | if (!( id.indexOf("GIF") == 0 ) ) 714 | { 715 | status = STATUS_FORMAT_ERROR; 716 | throw new FileTypeError ( "Invalid file type" ); 717 | return; 718 | } 719 | readLSD(); 720 | if (gctFlag && !hasError()) 721 | { 722 | gct = readColorTable(gctSize); 723 | bgColor = gct[bgIndex]; 724 | } 725 | } 726 | 727 | /** 728 | * Reads next frame image 729 | */ 730 | private function readImage():void 731 | { 732 | ix = readShort(); // (sub)image position & size 733 | iy = readShort(); 734 | iw = readShort(); 735 | ih = readShort(); 736 | 737 | var packed:int = readSingleByte(); 738 | lctFlag = (packed & 0x80) != 0; // 1 - local color table flag 739 | interlace = (packed & 0x40) != 0; // 2 - interlace flag 740 | // 3 - sort flag 741 | // 4-5 - reserved 742 | lctSize = 2 << (packed & 7); // 6-8 - local color table size 743 | 744 | if (lctFlag) 745 | { 746 | lct = readColorTable(lctSize); // read table 747 | act = lct; // make local table active 748 | } 749 | else 750 | { 751 | act = gct; // make global table active 752 | if (bgIndex == transIndex) 753 | bgColor = 0; 754 | } 755 | var save:int = 0; 756 | if (transparency) 757 | { 758 | save = act[transIndex]; 759 | act[transIndex] = 0; // set transparent color if specified 760 | } 761 | 762 | if (act == null) 763 | { 764 | status = STATUS_FORMAT_ERROR; // no color table defined 765 | } 766 | 767 | if (hasError()) return; 768 | 769 | decodeImageData(); // decode pixel data 770 | skip(); 771 | if (hasError()) return; 772 | 773 | frameCount++; 774 | // create new image to receive frame data 775 | 776 | bitmap = new BitmapData ( width, height ); 777 | 778 | image = bitmap; 779 | 780 | transferPixels(); // transfer pixel data to image 781 | 782 | frames.push ( new GIFFrame (bitmap, delay, lastDispose) ); // add image to frame list 783 | 784 | if (transparency) act[transIndex] = save; 785 | 786 | resetFrame(); 787 | 788 | } 789 | 790 | /** 791 | * Reads Logical Screen Descriptor 792 | */ 793 | private function readLSD():void 794 | { 795 | 796 | // logical screen size 797 | width = readShort(); 798 | height = readShort(); 799 | 800 | // packed fields 801 | var packed:int = readSingleByte(); 802 | 803 | gctFlag = (packed & 0x80) != 0; // 1 : global color table flag 804 | // 2-4 : color resolution 805 | // 5 : gct sort flag 806 | gctSize = 2 << (packed & 7); // 6-8 : gct size 807 | bgIndex = readSingleByte(); // background color index 808 | pixelAspect = readSingleByte(); // pixel aspect ratio 809 | 810 | } 811 | 812 | /** 813 | * Reads Netscape extenstion to obtain iteration count 814 | */ 815 | private function readNetscapeExt():void 816 | { 817 | do 818 | { 819 | readBlock(); 820 | if (block[0] == 1) 821 | { 822 | // loop count sub-block 823 | var b1:int = (block[1]) & 0xff; 824 | var b2:int = (block[2]) & 0xff; 825 | loopCount = (b2 << 8) | b1; 826 | } 827 | } while ((blockSize > 0) && !hasError()); 828 | } 829 | 830 | /** 831 | * Reads next 16-bit value, LSB first 832 | */ 833 | private function readShort():int 834 | { 835 | // read 16-bit value, LSB first 836 | return readSingleByte() | (readSingleByte() << 8); 837 | } 838 | 839 | /** 840 | * Resets frame state for reading next image. 841 | */ 842 | private function resetFrame():void 843 | { 844 | lastDispose = dispose; 845 | lastRect = new Rectangle(ix, iy, iw, ih); 846 | lastImage = image; 847 | lastBgColor = bgColor; 848 | // int dispose = 0; 849 | var transparency:Boolean = false; 850 | var delay:int = 0; 851 | lct = null; 852 | } 853 | 854 | /** 855 | * Skips variable length blocks up to and including 856 | * next zero length block. 857 | */ 858 | private function skip():void 859 | { 860 | do 861 | { 862 | readBlock(); 863 | 864 | } while ((blockSize > 0) && !hasError()); 865 | } 866 | } 867 | 868 | } --------------------------------------------------------------------------------