package org.alivepdf.encoding
{
    import flash.display.BitmapData;
    import flash.filters.ColorMatrixFilter;
    import flash.geom.Point;
    import flash.utils.ByteArray;
    import org.alivepdf.encoding.BitString;
    import org.alivepdf.encoding.IntBlock;
    import org.alivepdf.encoding.IntList;
    /**
     * Class that converts BitmapData into a valid JPEG
     */
    public final class JPEGEncoder {

        // Static table initialization

        private static const ZigZagList:IntList = IntList.create([
             0, 1, 5, 6,14,15,27,28,
             2, 4, 7,13,16,26,29,42,
             3, 8,12,17,25,30,41,43,
             9,11,18,24,31,40,44,53,
            10,19,23,32,39,45,52,54,
            20,22,33,38,46,51,55,60,
            21,34,37,47,50,56,59,61,
            35,36,48,49,57,58,62,63
        ]);

        private static const YQTList:IntList = IntList.create([
            16, 11, 10, 16, 24, 40, 51, 61,
            12, 12, 14, 19, 26, 58, 60, 55,
            14, 13, 16, 24, 40, 57, 69, 56,
            14, 17, 22, 29, 51, 87, 80, 62,
            18, 22, 37, 56, 68,109,103, 77,
            24, 35, 55, 64, 81,104,113, 92,
            49, 64, 78, 87,103,121,120,101,
            72, 92, 95, 98,112,100,103, 99
        ]);
        private static const UVQTList:IntList = IntList.create([
            17, 18, 24, 47, 99, 99, 99, 99,
            18, 21, 26, 66, 99, 99, 99, 99,
            24, 26, 56, 99, 99, 99, 99, 99,
            47, 66, 99, 99, 99, 99, 99, 99,
            99, 99, 99, 99, 99, 99, 99, 99,
            99, 99, 99, 99, 99, 99, 99, 99,
            99, 99, 99, 99, 99, 99, 99, 99,
            99, 99, 99, 99, 99, 99, 99, 99
        ]);
        private static const aasf:Array = [
            1.0, 1.387039845, 1.306562965, 1.175875602,
            1.0, 0.785694958, 0.541196100, 0.275899379
        ];
        private static const aanscalesList:IntList = IntList.create([
            /* precomputed values scaled up by 14 bits */
            16384, 22725, 21407, 19266, 16384, 12873,  8867,  4520,
            22725, 31521, 29692, 26722, 22725, 17855, 12299,  6270,
            21407, 29692, 27969, 25172, 21407, 16819, 11585,  5906,
            19266, 26722, 25172, 22654, 19266, 15137, 10426,  5315,
            16384, 22725, 21407, 19266, 16384, 12873,  8867,  4520,
            12873, 17855, 16819, 15137, 12873, 10114,  6967,  3552,
             8867, 12299, 11585, 10426,  8867,  6967,  4799,  2446,
             4520,  6270,  5906,  5315,  4520,  3552,  2446,  1247
        ]);

        private const YTable:Array = new Array(64);
        private const UVTable:Array = new Array(64);
        private const fdtbl_YList:IntList = IntList.create(new Array(64));
        private const fdtbl_UVList:IntList = IntList.create(new Array(64));

        private function initQuantTables(sf:int):void {
            var i:int;
            var t:int;
            var ZigZag:IntList = ZigZagList;
            var YQT:IntList = YQTList;
            for (i = 0; i < 64; ++i) {
                t = ((YQTList.data*sf+50)/100);
                YQT = YQT.next;
                if (t < 1) {
                    t = 1;
                } else if (t > 255) {
                    t = 255;
                }
                YTable[ZigZag.data] = t;
                ZigZag = ZigZag.next;
            }
            ZigZag = ZigZagList;
            var UVQT:IntList = UVQTList;
            for (i = 0; i < 64; ++i) {
                t = ((UVQT.data*sf+50)/100);
                UVQT = UVQT.next;
                if (t < 1) {
                    t = 1;
                } else if (t > 255) {
                    t = 255;
                }
                UVTable[ZigZag.data] = t;
                ZigZag = ZigZag.next;
            }
            ZigZag = ZigZagList;
            var fdtbl_Y:IntList = fdtbl_YList;
            var fdtbl_UV:IntList = fdtbl_UVList;
            for (i = 0; i < 64; ++i) {
                fdtbl_Y.data  =  YTable[ZigZag.data] << 3;
                fdtbl_UV.data = UVTable[ZigZag.data] << 3;
                ZigZag = ZigZag.next;
                fdtbl_Y = fdtbl_Y.next;
                fdtbl_UV = fdtbl_UV.next;
            }
        }

        private static const std_dc_luminance_nrcodesList:IntList = IntList.create([0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0]);
        private static const std_dc_luminance_valuesList:IntList = IntList.create([0,1,2,3,4,5,6,7,8,9,10,11]);
        private static const std_ac_luminance_nrcodesList:IntList = IntList.create([0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d]);
        private static const std_ac_luminance_valuesList:IntList = IntList.create([
            0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,
            0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,
            0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,
            0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,
            0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,
            0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,
            0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,
            0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,
            0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,
            0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,
            0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,
            0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,
            0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,
            0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,
            0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,
            0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,
            0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,
            0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,
            0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,
            0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
            0xf9,0xfa
        ]);

        private static const std_dc_chrominance_nrcodesList:IntList = IntList.create([0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0]);
        private static const std_dc_chrominance_valuesList:IntList = IntList.create([0,1,2,3,4,5,6,7,8,9,10,11]);
        private static const std_ac_chrominance_nrcodesList:IntList = IntList.create([0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77]);
        private static const std_ac_chrominance_valuesList:IntList = IntList.create([
            0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,
            0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,
            0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,
            0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,
            0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,
            0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,
            0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,
            0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,
            0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,
            0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,
            0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,
            0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,
            0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,
            0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,
            0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,
            0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,
            0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,
            0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,
            0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,
            0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
            0xf9,0xfa
        ]);

        private function computeHuffmanTbl(nrcodesList:IntList, std_tableList:IntList):Array {
            var codevalue:int = 0;
            var nrcodes:IntList = nrcodesList.next;
            var std_table:IntList = std_tableList;
        //    var pos_in_table:int = 0;
            var HT:Array = new Array(251);
            for (var k:int = 1; k <= 16; ++k) {
                var nr:int = nrcodes.data;
                for (var j:int=1; j<=nr; ++j) {
                    HT[std_table.data] = new BitString(codevalue, k);
                    std_table = std_table.next;
        //            ++pos_in_table;
                    ++codevalue;
                }
                nrcodes = nrcodes.next;
                codevalue<<=1;
            }
            return HT;
        }

        private var YDC_HT:Array;
        private var UVDC_HT:Array;
        private var YAC_HT:Array;
        private var UVAC_HT:Array;

        private function initHuffmanTbl():void {
            YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodesList,std_dc_luminance_valuesList);
            UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodesList,std_dc_chrominance_valuesList);
            YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodesList,std_ac_luminance_valuesList);
            UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodesList,std_ac_chrominance_valuesList);
        }

        private const bitcode:Array = new Array(65535);
        private const category:Array = new Array(65535);

        private function initCategoryNumber():void {
            var nrlower:int = 1;
            var nrupper:int = 2;
            var nr:int;
            var n:int;
            for (var cat:int=1; cat<=15; ++cat) {
                //Positive numbers
                for (nr=nrlower; nr<nrupper; ++nr) {
                    n = 32767+nr;
                    category[n] = cat;
                    bitcode[n] = new BitString(nr, cat);
                }
                //Negative numbers
                for (nr=-(nrupper-1); nr<=-nrlower; ++nr) {
                    n = 32767+nr;
                    category[n] = cat;
                    bitcode[n] = new BitString(nrupper-1+nr, cat);
                }
                nrlower <<= 1;
                nrupper <<= 1;
            }
        }

        // IO functions

        private var byteout:ByteArray;
        private var bytenew:int = 0;
        private var bytepos:int = 7;

        private function writeBits(bs:BitString):void {
            var value:int = bs.val;
            var posval:int = bs.len-1;
            while ( posval >= 0 ) {
                if (value & (1 << posval) ) {
                    bytenew |= (1 << bytepos);
                }
                posval--;
                bytepos--;
                if (bytepos < 0) {
                    if (bytenew == 0xFF) {
                        writeByte(0xFF);
                        writeByte(0);
                    }
                    else {
                        writeByte(bytenew);
                    }
                    bytepos=7;
                    bytenew=0;
                }
            }
        }

        private function writeByte(value:int):void {
            byteout.writeByte(value);
        }

        private function writeWord(value:int):void {
            writeByte((value>>8));
            writeByte((value   ));
        }

        // DCT & quantization core

//#define FIX_0_298631336  ((INT32)  2446)    /* FIX(0.298631336) */
//#define FIX_0_390180644  ((INT32)  3196)    /* FIX(0.390180644) */
//#define FIX_0_541196100  ((INT32)  4433)    /* FIX(0.541196100) */
//#define FIX_0_765366865  ((INT32)  6270)    /* FIX(0.765366865) */
//#define FIX_0_899976223  ((INT32)  7373)    /* FIX(0.899976223) */
//#define FIX_1_175875602  ((INT32)  9633)    /* FIX(1.175875602) */
//#define FIX_1_501321110  ((INT32)  12299)    /* FIX(1.501321110) */
//#define FIX_1_847759065  ((INT32)  15137)    /* FIX(1.847759065) */
//#define FIX_1_961570560  ((INT32)  16069)    /* FIX(1.961570560) */
//#define FIX_2_053119869  ((INT32)  16819)    /* FIX(2.053119869) */
//#define FIX_2_562915447  ((INT32)  20995)    /* FIX(2.562915447) */
//#define FIX_3_072711026  ((INT32)  25172)    /* FIX(3.072711026) */
        private function fDCTQuant(data:IntBlock, fdtbl:IntList):IntBlock {
            var tmp0:int, tmp1:int, tmp2:int, tmp3:int, tmp4:int, tmp5:int, tmp6:int, tmp7:int;
            var tmp10:int, tmp11:int, tmp12:int, tmp13:int;
            var d0:int, d1:int, d2:int, d3:int, d4:int, d5:int, d6:int, d7:int;
            var z1:int, z2:int, z3:int, z4:int, z5:int;
            var i:int;
            var row:IntBlock, col:IntBlock;
            var dataOff:IntBlock;
            /* Pass 1: process rows. */
            /* Note results are scaled up by sqrt(8) compared to a true DCT; */
            /* furthermore, we scale the results by 2**2. */
            row = data;
            for (i=0; i<8; ++i) {
                dataOff = row;
                d0 = dataOff.data;
                dataOff = dataOff.next;
                d1 = dataOff.data;
                dataOff = dataOff.next;
                d2 = dataOff.data;
                dataOff = dataOff.next;
                d3 = dataOff.data;
                dataOff = dataOff.next;
                d4 = dataOff.data;
                dataOff = dataOff.next;
                d5 = dataOff.data;
                dataOff = dataOff.next;
                d6 = dataOff.data;
                dataOff = dataOff.next;
                d7 = dataOff.data;

                tmp0 = d0+d7;
                tmp7 = d0-d7;
                tmp1 = d1+d6;
                tmp6 = d1-d6;
                tmp2 = d2+d5;
                tmp5 = d2-d5;
                tmp3 = d3+d4;
                tmp4 = d3-d4;

                /* Even part per LL&M figure 1 --- note that published figure is faulty;
                 * rotator "sqrt(2)*c1" should be "sqrt(2)*c6".
                 */
                tmp10 = tmp0 + tmp3;
                tmp13 = tmp0 - tmp3;
                tmp11 = tmp1 + tmp2;
                tmp12 = tmp1 - tmp2;

                z1 = ((tmp12 + tmp13) * /*FIX_0_541196100*/4433);

                dataOff = row;
                dataOff.data = (tmp10 + tmp11) << 2;
                dataOff = dataOff.next.next;
                dataOff.data = (z1 + tmp13 * /*FIX_0_765366865*/6270 + (/*1 << 10*/0x400)) >> 11;
                dataOff = dataOff.next.next;
                dataOff.data = (tmp10 - tmp11) << 2;
                dataOff = dataOff.next.next;
                dataOff.data = (z1 - tmp12 * /*FIX_1_847759065*/15137 + (/*1 << 10*/0x400)) >> 11;

                /* Odd part per figure 8 --- note paper omits factor of sqrt(2).
                 * cK represents cos(K*pi/16).
                 * i0..i3 in the paper are tmp4..tmp7 here.
                 */
                z1 = tmp4 + tmp7;
                z2 = tmp5 + tmp6;
                z3 = tmp4 + tmp6;
                z4 = tmp5 + tmp7;
                z5 = (z3 + z4) * /*FIX_1_175875602*/9633; /* sqrt(2) * c3 */

                tmp4 = tmp4 * /*FIX_0_298631336*/2446; /* sqrt(2) * (-c1+c3+c5-c7) */
                tmp5 = tmp5 * /*FIX_2_053119869*/16819; /* sqrt(2) * ( c1+c3-c5+c7) */
                tmp6 = tmp6 * /*FIX_3_072711026*/25172; /* sqrt(2) * ( c1+c3+c5-c7) */
                tmp7 = tmp7 * /*FIX_1_501321110*/12299; /* sqrt(2) * ( c1+c3-c5-c7) */
                z1 = - z1 * /*FIX_0_899976223*/7373; /* sqrt(2) * (c7-c3) */
                z2 = - z2 * /*FIX_2_562915447*/20995; /* sqrt(2) * (-c1-c3) */
                z3 = - z3 * /*FIX_1_961570560*/16069; /* sqrt(2) * (-c3-c5) */
                z4 = - z4 * /*FIX_0_390180644*/3196; /* sqrt(2) * (c5-c3) */

                z3 += z5;
                z4 += z5;

                dataOff = row.next;
                dataOff.data = (tmp7 + z1 + z4 + (/*1 << 10*/0x400)) >> 11;
                dataOff = dataOff.next.next;
                dataOff.data = (tmp6 + z2 + z3 + (/*1 << 10*/0x400)) >> 11;
                dataOff = dataOff.next.next;
                dataOff.data = (tmp5 + z2 + z4 + (/*1 << 10*/0x400)) >> 11;
                dataOff = dataOff.next.next;
                dataOff.data = (tmp4 + z1 + z3 + (/*1 << 10*/0x400)) >> 11;

                row = row.down; /* advance pointer to next row */
            }

            /* Pass 2: process columns.
             * We remove the PASS1_BITS scaling, but leave the results scaled up
             * by an overall factor of 8.
             */
            col = data;
            for (i=0; i<8; ++i) {
                dataOff = col;
                d0 = dataOff.data;
                dataOff = dataOff.down;
                d1 = dataOff.data;
                dataOff = dataOff.down;
                d2 = dataOff.data;
                dataOff = dataOff.down;
                d3 = dataOff.data;
                dataOff = dataOff.down;
                d4 = dataOff.data;
                dataOff = dataOff.down;
                d5 = dataOff.data;
                dataOff = dataOff.down;
                d6 = dataOff.data;
                dataOff = dataOff.down;
                d7 = dataOff.data;

                tmp0 = d0+d7;
                tmp7 = d0-d7;
                tmp1 = d1+d6;
                tmp6 = d1-d6;
                tmp2 = d2+d5;
                tmp5 = d2-d5;
                tmp3 = d3+d4;
                tmp4 = d3-d4;

                /* Even part per LL&M figure 1 --- note that published figure is faulty;
                 * rotator "sqrt(2)*c1" should be "sqrt(2)*c6".
                 */
                tmp10 = tmp0 + tmp3;
                tmp13 = tmp0 - tmp3;
                tmp11 = tmp1 + tmp2;
                tmp12 = tmp1 - tmp2;

                z1 = ((tmp12 + tmp13) * /*FIX_0_541196100*/4433);

                dataOff = col;
                dataOff.data = (tmp10 + tmp11 + (/*1 << 1*/0x2)) >> 2;
                dataOff = dataOff.down.down;
                dataOff.data = (z1 + tmp13 * /*FIX_0_765366865*/6270 + (/*1 << 14*/0x4000)) >> 15;
                dataOff = dataOff.down.down;
                dataOff.data = (tmp10 - tmp11 + (/*1 << 1*/0x2)) >> 2;
                dataOff = dataOff.down.down;
                dataOff.data = (z1 - tmp12 * /*FIX_1_847759065*/15137 + (/*1 << 14*/0x4000)) >> 15;

                /* Odd part per figure 8 --- note paper omits factor of sqrt(2).
                 * cK represents cos(K*pi/16).
                 * i0..i3 in the paper are tmp4..tmp7 here.
                 */
                z1 = tmp4 + tmp7;
                z2 = tmp5 + tmp6;
                z3 = tmp4 + tmp6;
                z4 = tmp5 + tmp7;
                z5 = (z3 + z4) * /*FIX_1_175875602*/9633; /* sqrt(2) * c3 */

                tmp4 = tmp4 * /*FIX_0_298631336*/2446; /* sqrt(2) * (-c1+c3+c5-c7) */
                tmp5 = tmp5 * /*FIX_2_053119869*/16819; /* sqrt(2) * ( c1+c3-c5+c7) */
                tmp6 = tmp6 * /*FIX_3_072711026*/25172; /* sqrt(2) * ( c1+c3+c5-c7) */
                tmp7 = tmp7 * /*FIX_1_501321110*/12299; /* sqrt(2) * ( c1+c3-c5-c7) */
                z1 = - z1 * /*FIX_0_899976223*/7373; /* sqrt(2) * (c7-c3) */
                z2 = - z2 * /*FIX_2_562915447*/20995; /* sqrt(2) * (-c1-c3) */
                z3 = - z3 * /*FIX_1_961570560*/16069; /* sqrt(2) * (-c3-c5) */
                z4 = - z4 * /*FIX_0_390180644*/3196; /* sqrt(2) * (c5-c3) */

                z3 += z5;
                z4 += z5;

                dataOff = col.down;
                dataOff.data = (tmp7 + z1 + z4 + (/*1 << 14*/0x4000)) >> 15;
                dataOff = dataOff.down.down;
                dataOff.data = (tmp6 + z2 + z3 + (/*1 << 14*/0x4000)) >> 15;
                dataOff = dataOff.down.down;
                dataOff.data = (tmp5 + z2 + z4 + (/*1 << 14*/0x4000)) >> 15;
                dataOff = dataOff.down.down;
                dataOff.data = (tmp4 + z1 + z3 + (/*1 << 14*/0x4000)) >> 15;

                col = col.next; /* advance pointer to next column */
            }

            // Quantize/descale the coefficients
            dataOff = data;
            for (i=0; i<64; ++i) {
                // Apply the quantization and scaling factor & Round to nearest integer
                var qval:int = fdtbl.data;
                fdtbl = fdtbl.next;
                var temp:int = dataOff.data;
                if (temp < 0) {
                    temp = -temp;
                    temp += qval >> 1;    /* for rounding */
                    if (temp >= qval) temp /= qval;
                    else temp = 0;
                    temp = -temp;
                } else {
                    temp += qval >> 1;    /* for rounding */
                    if (temp >= qval) temp /= qval;
                    else temp = 0;
                }
                dataOff.data = temp;
                dataOff = dataOff.next;
            }
            return data;
        }

        // Chunk writing

        private function writeAPP0():void {
            writeWord(0xFFE0); // marker
            writeWord(16); // length
            writeByte(0x4A); // J
            writeByte(0x46); // F
            writeByte(0x49); // I
            writeByte(0x46); // F
            writeByte(0); // = "JFIF",'\0'
            writeByte(1); // versionhi
            writeByte(1); // versionlo
            writeByte(0); // xyunits
            writeWord(1); // xdensity
            writeWord(1); // ydensity
            writeByte(0); // thumbnwidth
            writeByte(0); // thumbnheight
        }

        private function writeSOF0(width:int, height:int):void {
            writeWord(0xFFC0); // marker
            writeWord(17);   // length, truecolor YUV JPG
            writeByte(8);    // precision
            writeWord(height);
            writeWord(width);
            writeByte(3);    // nrofcomponents
            writeByte(1);    // IdY
            writeByte(0x11); // HVY
            writeByte(0);    // QTY
            writeByte(2);    // IdU
            writeByte(0x11); // HVU
            writeByte(1);    // QTU
            writeByte(3);    // IdV
            writeByte(0x11); // HVV
            writeByte(1);    // QTV
        }

        private function writeDQT():void {
            writeWord(0xFFDB); // marker
            writeWord(132);       // length
            writeByte(0);
            var i:int;
            for (i=0; i<64; ++i) {
                writeByte(YTable[i]);
            }
            writeByte(1);
            for (i=0; i<64; ++i) {
                writeByte(UVTable[i]);
            }
        }

        private function writeDHT():void {
            writeWord(0xFFC4); // marker
            writeWord(0x01A2); // length
            var i:int;

            writeByte(0); // HTYDCinfo
            var std_dc_luminance_nrcodes:IntList = std_dc_luminance_nrcodesList.next;
            for (i=1; i<=16; ++i) {
                writeByte(std_dc_luminance_nrcodes.data);
                std_dc_luminance_nrcodes = std_dc_luminance_nrcodes.next;
            }
            var std_dc_luminance_values:IntList = std_dc_luminance_valuesList;
            for (i=0; i<=11; ++i) {
                writeByte(std_dc_luminance_values.data);
                std_dc_luminance_values = std_dc_luminance_values.next;
            }

            writeByte(0x10); // HTYACinfo
            var std_ac_luminance_nrcodes:IntList = std_ac_luminance_nrcodesList.next;
            for (i=1; i<=16; ++i) {
                writeByte(std_ac_luminance_nrcodes.data);
                std_ac_luminance_nrcodes = std_ac_luminance_nrcodes.next;
            }
            var std_ac_luminance_values:IntList = std_ac_luminance_valuesList;
            for (i=0; i<=161; ++i) {
                writeByte(std_ac_luminance_values.data);
                std_ac_luminance_values = std_ac_luminance_values.next;
            }

            writeByte(1); // HTUDCinfo
            var std_dc_chrominance_nrcodes:IntList = std_dc_chrominance_nrcodesList.next;
            for (i=1; i<=16; ++i) {
                writeByte(std_dc_chrominance_nrcodes.data);
                std_dc_chrominance_nrcodes = std_dc_chrominance_nrcodes.next;
            }
            var std_dc_chrominance_values:IntList = std_dc_chrominance_valuesList;
            for (i=0; i<=11; ++i) {
                writeByte(std_dc_chrominance_values.data);
                std_dc_chrominance_values = std_dc_chrominance_values.next;
            }

            writeByte(0x11); // HTUACinfo
            var std_ac_chrominance_nrcodes:IntList = std_ac_chrominance_nrcodesList.next;
            for (i=1; i<=16; ++i) {
                writeByte(std_ac_chrominance_nrcodes.data);
                std_ac_chrominance_nrcodes = std_ac_chrominance_nrcodes.next;
            }
            var std_ac_chrominance_values:IntList = std_ac_chrominance_valuesList;
            for (i=0; i<=161; ++i) {
                writeByte(std_ac_chrominance_values.data);
                std_ac_chrominance_values = std_ac_chrominance_values.next;
            }
        }

        private function writeSOS():void {
            writeWord(0xFFDA); // marker
            writeWord(12); // length
            writeByte(3); // nrofcomponents
            writeByte(1); // IdY
            writeByte(0); // HTY
            writeByte(2); // IdU
            writeByte(0x11); // HTU
            writeByte(3); // IdV
            writeByte(0x11); // HTV
            writeByte(0); // Ss
            writeByte(0x3f); // Se
            writeByte(0); // Bf
        }

        // Core processing
        private const DU:Array = new Array(64);

        private function processDU(CDU:IntBlock, fdtbl:IntList, DC:int, HTDC:Array, HTAC:Array):int {
            var EOB:BitString = HTAC[0x00];
            var M16zeroes:BitString = HTAC[0xF0];
            var i:int;

            var DU_DCT:IntBlock = fDCTQuant(CDU, fdtbl);
            //ZigZag reorder
            var ZigZag:IntList = ZigZagList;
            for (i=0;i<64;++i) {
                DU[ZigZag.data] = DU_DCT.data;
                ZigZag = ZigZag.next;
                DU_DCT = DU_DCT.next;
            }
            var Diff:int = DU[0] - DC; DC = DU[0];
            //Encode DC
            if (Diff==0) {
                writeBits(HTDC[0]); // Diff might be 0
            } else {
                i = 32767+Diff;
                writeBits(HTDC[category[i]>>0]);
                writeBits(bitcode[i]);
            }
            //Encode ACs
            var end0pos:int = 63;
            while((end0pos>0)&&(DU[end0pos]==0)) --end0pos;
            //end0pos = first element in reverse order !=0
            if ( end0pos == 0) {
                writeBits(EOB);
                return DC;
            }
            i = 1;
            while ( i <= end0pos ) {
                var startpos:int = i;
                while((DU[i]==0) && (i<=end0pos)) ++i;
                var nrzeroes:int = i-startpos;
                var n:int;
                if ( nrzeroes >= 16 ) {
                    n = nrzeroes/16;
                    for (var nrmarker:int=1; nrmarker <= n; ++nrmarker) {
                        writeBits(M16zeroes);
                    }
                    nrzeroes = (nrzeroes&0xF);
                }
                n = 32767+DU[i];
                writeBits(HTAC[((nrzeroes<<4)+category[n])>>0]);
                writeBits(bitcode[n]);
                ++i;
            }
            if ( end0pos != 63 ) {
                writeBits(EOB);
            }
            return DC;
        }

        private const YDUBlock:IntBlock = IntBlock.create8_8(new Array(64));
        private const UDUBlock:IntBlock = IntBlock.create8_8(new Array(64));
        private const VDUBlock:IntBlock = IntBlock.create8_8(new Array(64));
        private static const fltrRGB2YUV:ColorMatrixFilter = new ColorMatrixFilter([
             0.29900,  0.58700,  0.11400, 0,   0,
            -0.16874, -0.33126,  0.50000, 0, 128,
             0.50000, -0.41869, -0.08131, 0, 128,
                   0,        0,        0, 1,   0
        ]);
        private static const orgn:Point = new Point();

        //private static const rgb_ycc_tab:Array = new Array(2048);
        //private function init_rgb_ycc_tab():void {
        //    for (var i:int = 0; i <= 255; i++) {
        //        rgb_ycc_tab[i]      =  19595 * i;
        //        rgb_ycc_tab[(i+ 256)>>0] =  38470 * i;
        //        rgb_ycc_tab[(i+ 512)>>0] =   7471 * i + 0x8000;
        //        rgb_ycc_tab[(i+ 768)>>0] = -11059 * i;
        //        rgb_ycc_tab[(i+1024)>>0] = -21709 * i;
                /* We use a rounding fudge-factor of 0.5-epsilon for Cb and Cr.
                 * This ensures that the maximum output will round to MAXJSAMPLE
                 * not MAXJSAMPLE+1, and thus that we don't have to range-limit.
                 */
        //        rgb_ycc_tab[(i+1280)>>0] =  32768 * i + 0x807FFF;
                /*  B=>Cb and R=>Cr tables are the same
                    rgb_ycc_tab[i+R_CR_OFF] = FIX(0.50000) * i    + CBCR_OFFSET + ONE_HALF-1;
                */
        //        rgb_ycc_tab[(i+1536)>>0] = -27439 * i;
        //        rgb_ycc_tab[(i+1792)>>0] = - 5329 * i;
        //    }
        //}

        private function RGB2YUV(img:BitmapData, xpos:int, ypos:int):void {
            var YDU:IntBlock = YDUBlock;
            var UDU:IntBlock = UDUBlock;
            var VDU:IntBlock = VDUBlock;
        //    var pos:int=0;
            for (var y:int=0; y<8; ++y) {
                for (var x:int=0; x<8; ++x) {
                    var P:int = img.getPixel(xpos+x,ypos+y);
                    var R:int = ((P>>16)&0xFF);
                    var G:int = ((P>> 8)&0xFF);
                    var B:int = ((P    )&0xFF);
                    /* RGB2YUV with ColorMatrixFilter */
                    YDU.data = R-128;
                    UDU.data = G-128;
                    VDU.data = B-128;
                    /* float RGB2YUV without ColorMatrixFilter
                    YDU[pos] = ((( 0.29900) * R + ( 0.58700) * G + ( 0.11400) * B)) - 128;
                    UDU[pos] = ((( -0.16874) * R + ( -0.33126) * G + ( 0.50000) * B));
                    VDU[pos] = ((( 0.50000) * R + ( -0.41869) * G + ( -0.08131) * B));
                    */
                    /* precalculated RGB2YUV without ColorMatrixFilter
                    YDU[pos] = ((rgb_ycc_tab[R]             + rgb_ycc_tab[(G +  256)>>0] + rgb_ycc_tab[(B +  512)>>0]) >> 16)-128;
                    UDU[pos] = ((rgb_ycc_tab[(R +  768)>>0] + rgb_ycc_tab[(G + 1024)>>0] + rgb_ycc_tab[(B + 1280)>>0]) >> 16)-128;
                    VDU[pos] = ((rgb_ycc_tab[(R + 1280)>>0] + rgb_ycc_tab[(G + 1536)>>0] + rgb_ycc_tab[(B + 1792)>>0]) >> 16)-128;
                    */
                    YDU = YDU.next;
                    UDU = UDU.next;
                    VDU = VDU.next;
        //            ++pos;
                }
            }
        }

        /**
         * Constructor for JPEGEncoder class
         *
         * @param quality The quality level between 1 and 100 that detrmines the
         * level of compression used in the generated JPEG
         * @param dct The forward DCT method to use,
         * supported methods: JDCT_ISLOW, JDCT_IFAST, JDCT_FLOAT
         * @langversion ActionScript 3.0
         * @playerversion Flash 9.0
         * @tiptext
         */
        public function JPEGEncoder(quality:Number = 50) {
            if (quality <= 0) {
                quality = 1;
            }
            if (quality > 100) {
                quality = 100;
            }
            var sf:int = 0;
            if (quality < 50) {
                sf = (5000 / quality);
            } else {
                sf = (200 - quality*2);
            }
            // Create tables
            initHuffmanTbl();
            initCategoryNumber();
            initQuantTables(sf);
            //init_rgb_ycc_tab();
        }

        /**
         * Created a JPEG image from the specified BitmapData
         *
         * @param image The BitmapData that will be converted into the JPEG format.
         * @return a ByteArray representing the JPEG encoded image data.
         * @langversion ActionScript 3.0
         * @playerversion Flash 9.0
         * @tiptext
         */
        public function encode(image:BitmapData):ByteArray {
            //var img:BitmapData = image;
            var img:BitmapData = image.clone();
            img.applyFilter(img, img.rect, orgn, fltrRGB2YUV);
            var height:int = img.height;
            var width:int = img.width;

            // Initialize bit writer
            byteout = new ByteArray();
            bytenew=0;
            bytepos=7;

            // Add JPEG headers
            writeWord(0xFFD8); // SOI
            writeAPP0();
            writeDQT();
            writeSOF0(width,height);
            writeDHT();
            writeSOS();

            // Encode 8x8 macroblocks
            var DCY:int=0;
            var DCU:int=0;
            var DCV:int=0;

            for (var ypos:int=0; ypos<height; ypos+=8) {
                for (var xpos:int=0; xpos<width; xpos+=8) {
                    RGB2YUV(img, xpos, ypos);
                    DCY = processDU(YDUBlock, fdtbl_YList,  DCY,  YDC_HT,  YAC_HT);
                    DCU = processDU(UDUBlock, fdtbl_UVList, DCU, UVDC_HT, UVAC_HT);
                    DCV = processDU(VDUBlock, fdtbl_UVList, DCV, UVDC_HT, UVAC_HT);
                }
            }

            img.dispose();

            // Do the bit alignment of the EOI marker
            if ( bytepos >= 0 ) {
                writeBits(new BitString((1<<(bytepos+1))-1, bytepos+1));
            }

            writeWord(0xFFD9); //EOI
            return byteout;
        }
    }
}