{"id":943,"date":"2014-07-28T18:59:40","date_gmt":"2014-07-28T10:59:40","guid":{"rendered":"http:\/\/www.yeetrack.com\/?p=943"},"modified":"2023-11-23T09:48:48","modified_gmt":"2023-11-23T01:48:48","slug":"java%e7%94%9f%e6%88%90%e5%8a%a8%e6%80%81gif%e5%9b%be%e7%89%87","status":"publish","type":"post","link":"https:\/\/www.yeetrack.com\/?p=943","title":{"rendered":"Java\u751f\u6210\u52a8\u6001GIF\u56fe\u7247"},"content":{"rendered":"<p>\u5199selenium\u81ea\u52a8\u5316\u65f6\uff0c\u4e3a\u4e86\u67e5\u770b\u8fd0\u884c\u6548\u679c\uff0c\u540e\u7ed9\u6d4f\u89c8\u5668\u622a\u56fe\uff0c\u60f3\u5230\u53ef\u4ee5\u751f\u6210gif\u56fe\u7247\u6765\u5feb\u901f\u9884\u89c8\u3002\u770b\u5230\u5df2\u7ecf\u6709\u4eba\u5b9e\u73b0\u4e86\uff0c\u76f4\u63a5\u62ff\u8fc7\u6765\u3002\u4f5c\u8005\u662f<strong>Kevin Weiner<\/strong>\u3002<\/p>\n<p>\u5171\u6d89\u53ca\u5230\u4e09\u4e2ajava\u6587\u4ef6\uff0c\u5206\u522b\u662f<code>NeuQuant.java<\/code>,<code>LZWEncoder.java<\/code>,\u00a0<code>AnimatedGifEncoder.java<\/code>,\u6709\u4e86\u8fd9\u4e09\u4e2a\u6587\u4ef6\uff0c\u6211\u4eec\u53ef\u4ee5\u81ea\u5df1\u7f16\u5199\u65b9\u6cd5\u8c03\u7528<\/p>\n<p><a href=\"https:\/\/yeetrack-space.bj.bcebos.com\/blog\/201407\/\/chrome_com.yeetrack.selenium.test_.BrowserTest_baiduTest.gif\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-944\" src=\"https:\/\/yeetrack-space.bj.bcebos.com\/blog\/201407\/\/chrome_com.yeetrack.selenium.test_.BrowserTest_baiduTest.gif\" alt=\"chrome_com.yeetrack.selenium.test.BrowserTest_baiduTest\" width=\"1366\" height=\"732\" \/><\/a><\/p>\n<p><!--more-->\uff0c\u4ee3\u7801\u5982\u4e0b\uff1a<\/p>\n<pre><code>        BufferedImage src1 = ImageIO.read(new File(\"Img221785570.jpg\"));\n        BufferedImage src2 = ImageIO.read(new File(\"W.gif\"));\n        \/\/BufferedImage src3 = ImageIO.read(new File(\"c:\/ship3.jpg\")); \n        AnimatedGifEncoder e = new AnimatedGifEncoder(); \n        e.setRepeat(0); \n        e.start(\"laoma.gif\"); \n        e.setDelay(300); \/\/ 1 frame per sec \n        e.addFrame(src1); \n        e.setDelay(100); \n        e.addFrame(src2); \n        e.setDelay(100); \n    \/\/  e.addFrame(src2); \n        e.finish(); \n<\/code><\/pre>\n<p>\u4e09\u4e2ajava\u6587\u4ef6\u6e90\u7801\u5982\u4e0b\uff1a<br \/>\n<code>NeuQuant.java<\/code><\/p>\n<pre><code>    public class NeuQuant\n    {\n        protected static final int netsize = 256; \/* number of colours used *\/\n        \/* four primes near 500 - assume no image has a length so large *\/\n     \/* that it is divisible by all four primes *\/\n        protected static final int prime1 = 499;\n        protected static final int prime2 = 491;\n        protected static final int prime3 = 487;\n        protected static final int prime4 = 503;\n        protected static final int minpicturebytes = (3 * prime4);\n        \/* minimum size for input image *\/\n     \/* Program Skeleton\n        ----------------\n        [select samplefac in range 1..30]\n        [read image from input file]\n        pic = (unsigned char*) malloc(3*width*height);\n        initnet(pic,3*width*height,samplefac);\n        learn();\n        unbiasnet();\n        [write output image header, using writecolourmap(f)]\n        inxbuild();\n        write output image using inxsearch(b,g,r)      *\/\n     \/* Network Definitions\n        ------------------- *\/\n        protected static final int maxnetpos = (netsize - 1);\n        protected static final int netbiasshift = 4; \/* bias for colour values *\/\n        protected static final int ncycles = 100; \/* no. of learning cycles *\/\n        \/* defs for freq and bias *\/\n        protected static final int intbiasshift = 16; \/* bias for fractions *\/\n        protected static final int intbias = (((int) 1) &lt;&lt; intbiasshift);\n        protected static final int gammashift = 10; \/* gamma = 1024 *\/\n        protected static final int gamma = (((int) 1) &lt;&lt; gammashift);\n        protected static final int betashift = 10;\n        protected static final int beta = (intbias &gt;&gt; betashift); \/* beta = 1\/1024 *\/\n        protected static final int betagamma =\n                (intbias &lt;&lt; (gammashift - betashift));\n        \/* defs for decreasing radius factor *\/\n        protected static final int initrad = (netsize &gt;&gt; 3); \/* for 256 cols, radius starts *\/\n        protected static final int radiusbiasshift = 6; \/* at 32.0 biased by 6 bits *\/\n        protected static final int radiusbias = (((int) 1) &lt;&lt; radiusbiasshift);\n        protected static final int initradius = (initrad * radiusbias); \/* and decreases by a *\/\n        protected static final int radiusdec = 30; \/* factor of 1\/30 each cycle *\/\n        \/* defs for decreasing alpha factor *\/\n        protected static final int alphabiasshift = 10; \/* alpha starts at 1.0 *\/\n        protected static final int initalpha = (((int) 1) &lt;&lt; alphabiasshift);\n        protected int alphadec; \/* biased by 10 bits *\/\n        \/* radbias and alpharadbias used for radpower calculation *\/\n        protected static final int radbiasshift = 8;\n        protected static final int radbias = (((int) 1) &lt;&lt; radbiasshift);\n        protected static final int alpharadbshift = (alphabiasshift + radbiasshift);\n        protected static final int alpharadbias = (((int) 1) &lt;&lt; alpharadbshift);\n        \/* Types and Global Variables\n        -------------------------- *\/\n        protected byte[] thepicture; \/* the input image itself *\/\n        protected int lengthcount; \/* lengthcount = H*W*3 *\/\n        protected int samplefac; \/* sampling factor 1..30 *\/\n        \/\/   typedef int pixel[4];                \/* BGRc *\/\n        protected int[][] network; \/* the network itself - [netsize][4] *\/\n        protected int[] netindex = new int[256];\n        \/* for network lookup - really 256 *\/\n        protected int[] bias = new int[netsize];\n        \/* bias and freq arrays for learning *\/\n        protected int[] freq = new int[netsize];\n        protected int[] radpower = new int[initrad];\n        \/* radpower for precomputation *\/\n     \/* Initialise network in range (0,0,0) to (255,255,255) and set parameters\n        ----------------------------------------------------------------------- *\/\n        public NeuQuant(byte[] thepic, int len, int sample) {\n            int i;\n            int[] p;\n            thepicture = thepic;\n            lengthcount = len;\n            samplefac = sample;\n            network = new int[netsize][];\n            for (i = 0; i &lt; netsize; i++) {\n                network[i] = new int[4];\n                p = network[i];\n                p[0] = p[1] = p[2] = (i &lt;&lt; (netbiasshift + 8)) \/ netsize;\n                freq[i] = intbias \/ netsize; \/* 1\/netsize *\/\n                bias[i] = 0;\n            }\n        }\n        public byte[] colorMap() {\n            byte[] map = new byte[3 * netsize];\n            int[] index = new int[netsize];\n            for (int i = 0; i &lt; netsize; i++)\n                index[network[i][3]] = i;\n            int k = 0;\n            for (int i = 0; i &lt; netsize; i++) {\n                int j = index[i];\n                map[k++] = (byte) (network[j][0]);\n                map[k++] = (byte) (network[j][1]);\n                map[k++] = (byte) (network[j][2]);\n            }\n            return map;\n        }\n        \/* Insertion sort of network and building of netindex[0..255] (to do after unbias)\n           ------------------------------------------------------------------------------- *\/\n        public void inxbuild() {\n            int i, j, smallpos, smallval;\n            int[] p;\n            int[] q;\n            int previouscol, startpos;\n            previouscol = 0;\n            startpos = 0;\n            for (i = 0; i &lt; netsize; i++) {\n                p = network[i];\n                smallpos = i;\n                smallval = p[1]; \/* index on g *\/\n       \/* find smallest in i..netsize-1 *\/\n                for (j = i + 1; j &lt; netsize; j++) {\n                    q = network[j];\n                    if (q[1] &lt; smallval) { \/* index on g *\/\n                        smallpos = j;\n                        smallval = q[1]; \/* index on g *\/\n                    }\n                }\n                q = network[smallpos];\n       \/* swap p (i) and q (smallpos) entries *\/\n                if (i != smallpos) {\n                    j = q[0];\n                    q[0] = p[0];\n                    p[0] = j;\n                    j = q[1];\n                    q[1] = p[1];\n                    p[1] = j;\n                    j = q[2];\n                    q[2] = p[2];\n                    p[2] = j;\n                    j = q[3];\n                    q[3] = p[3];\n                    p[3] = j;\n                }\n       \/* smallval entry is now in position i *\/\n                if (smallval != previouscol) {\n                    netindex[previouscol] = (startpos + i) &gt;&gt; 1;\n                    for (j = previouscol + 1; j &lt; smallval; j++)\n                        netindex[j] = i;\n                    previouscol = smallval;\n                    startpos = i;\n                }\n            }\n            netindex[previouscol] = (startpos + maxnetpos) &gt;&gt; 1;\n            for (j = previouscol + 1; j &lt; 256; j++)\n                netindex[j] = maxnetpos; \/* really 256 *\/\n        }\n        \/* Main Learning Loop\n           ------------------ *\/\n        public void learn() {\n            int i, j, b, g, r;\n            int radius, rad, alpha, step, delta, samplepixels;\n            byte[] p;\n            int pix, lim;\n            if (lengthcount &lt; minpicturebytes)\n                samplefac = 1;\n            alphadec = 30 + ((samplefac - 1) \/ 3);\n            p = thepicture;\n            pix = 0;\n            lim = lengthcount;\n            samplepixels = lengthcount \/ (3 * samplefac);\n            delta = samplepixels \/ ncycles;\n            alpha = initalpha;\n            radius = initradius;\n            rad = radius &gt;&gt; radiusbiasshift;\n            if (rad &lt;= 1)\n                rad = 0;\n            for (i = 0; i &lt; rad; i++)\n                radpower[i] =\n                        alpha * (((rad * rad - i * i) * radbias) \/ (rad * rad));\n            \/\/fprintf(stderr,\"beginning 1D learning: initial radius=%d\/n\", rad);\n            if (lengthcount &lt; minpicturebytes)\n                step = 3;\n            else if ((lengthcount % prime1) != 0)\n                step = 3 * prime1;\n            else {\n                if ((lengthcount % prime2) != 0)\n                    step = 3 * prime2;\n                else {\n                    if ((lengthcount % prime3) != 0)\n                        step = 3 * prime3;\n                    else\n                        step = 3 * prime4;\n                }\n            }\n            i = 0;\n            while (i &lt; samplepixels) {\n                b = (p[pix + 0] &amp; 0xff) &lt;&lt; netbiasshift;\n                g = (p[pix + 1] &amp; 0xff) &lt;&lt; netbiasshift;\n                r = (p[pix + 2] &amp; 0xff) &lt;&lt; netbiasshift;\n                j = contest(b, g, r);\n                altersingle(alpha, j, b, g, r);\n                if (rad != 0)\n                    alterneigh(rad, j, b, g, r); \/* alter neighbours *\/\n                pix += step;\n                if (pix &gt;= lim)\n                    pix -= lengthcount;\n                i++;\n                if (delta == 0)\n                    delta = 1;\n                if (i % delta == 0) {\n                    alpha -= alpha \/ alphadec;\n                    radius -= radius \/ radiusdec;\n                    rad = radius &gt;&gt; radiusbiasshift;\n                    if (rad &lt;= 1)\n                        rad = 0;\n                    for (j = 0; j &lt; rad; j++)\n                        radpower[j] =\n                                alpha * (((rad * rad - j * j) * radbias) \/ (rad * rad));\n                }\n            }\n            \/\/fprintf(stderr,\"finished 1D learning: final alpha=%f !\/n\",((float)alpha)\/initalpha);\n        }\n        \/* Search for BGR values 0..255 (after net is unbiased) and return colour index\n           ---------------------------------------------------------------------------- *\/\n        public int map(int b, int g, int r) {\n            int i, j, dist, a, bestd;\n            int[] p;\n            int best;\n            bestd = 1000; \/* biggest possible dist is 256*3 *\/\n            best = -1;\n            i = netindex[g]; \/* index on g *\/\n            j = i - 1; \/* start at netindex[g] and work outwards *\/\n            while ((i &lt; netsize) || (j &gt;= 0)) {\n                if (i &lt; netsize) {\n                    p = network[i];\n                    dist = p[1] - g; \/* inx key *\/\n                    if (dist &gt;= bestd)\n                        i = netsize; \/* stop iter *\/\n                    else {\n                        i++;\n                        if (dist &lt; 0)\n                            dist = -dist;\n                        a = p[0] - b;\n                        if (a &lt; 0)\n                            a = -a;\n                        dist += a;\n                        if (dist &lt; bestd) {\n                            a = p[2] - r;\n                            if (a &lt; 0)\n                                a = -a;\n                            dist += a;\n                            if (dist &lt; bestd) {\n                                bestd = dist;\n                                best = p[3];\n                            }\n                        }\n                    }\n                }\n                if (j &gt;= 0) {\n                    p = network[j];\n                    dist = g - p[1]; \/* inx key - reverse dif *\/\n                    if (dist &gt;= bestd)\n                        j = -1; \/* stop iter *\/\n                    else {\n                        j--;\n                        if (dist &lt; 0)\n                            dist = -dist;\n                        a = p[0] - b;\n                        if (a &lt; 0)\n                            a = -a;\n                        dist += a;\n                        if (dist &lt; bestd) {\n                            a = p[2] - r;\n                            if (a &lt; 0)\n                                a = -a;\n                            dist += a;\n                            if (dist &lt; bestd) {\n                                bestd = dist;\n                                best = p[3];\n                            }\n                        }\n                    }\n                }\n            }\n            return (best);\n        }\n        public byte[] process() {\n            learn();\n            unbiasnet();\n            inxbuild();\n            return colorMap();\n        }\n        \/* Unbias network to give byte values 0..255 and record position i to prepare for sort\n           ----------------------------------------------------------------------------------- *\/\n        public void unbiasnet() {\n            int i, j;\n            for (i = 0; i &lt; netsize; i++) {\n                network[i][0] &gt;&gt;= netbiasshift;\n                network[i][1] &gt;&gt;= netbiasshift;\n                network[i][2] &gt;&gt;= netbiasshift;\n                network[i][3] = i; \/* record colour no *\/\n            }\n        }\n        \/* Move adjacent neurons by precomputed alpha*(1-((i-j)^2\/[r]^2)) in radpower[|i-j|]\n           --------------------------------------------------------------------------------- *\/\n        protected void alterneigh(int rad, int i, int b, int g, int r) {\n            int j, k, lo, hi, a, m;\n            int[] p;\n            lo = i - rad;\n            if (lo &lt; -1)\n                lo = -1;\n            hi = i + rad;\n            if (hi &gt; netsize)\n                hi = netsize;\n            j = i + 1;\n            k = i - 1;\n            m = 1;\n            while ((j &lt; hi) || (k &gt; lo)) {\n                a = radpower[m++];\n                if (j &lt; hi) {\n                    p = network[j++];\n                    try {\n                        p[0] -= (a * (p[0] - b)) \/ alpharadbias;\n                        p[1] -= (a * (p[1] - g)) \/ alpharadbias;\n                        p[2] -= (a * (p[2] - r)) \/ alpharadbias;\n                    } catch (Exception e) {\n                    } \/\/ prevents 1.3 miscompilation\n                }\n                if (k &gt; lo) {\n                    p = network[k--];\n                    try {\n                        p[0] -= (a * (p[0] - b)) \/ alpharadbias;\n                        p[1] -= (a * (p[1] - g)) \/ alpharadbias;\n                        p[2] -= (a * (p[2] - r)) \/ alpharadbias;\n                    } catch (Exception e) {\n                    }\n                }\n            }\n        }\n        \/* Move neuron i towards biased (b,g,r) by factor alpha\n           ---------------------------------------------------- *\/\n        protected void altersingle(int alpha, int i, int b, int g, int r) {\n      \/* alter hit neuron *\/\n            int[] n = network[i];\n            n[0] -= (alpha * (n[0] - b)) \/ initalpha;\n            n[1] -= (alpha * (n[1] - g)) \/ initalpha;\n            n[2] -= (alpha * (n[2] - r)) \/ initalpha;\n        }\n        \/* Search for biased BGR values\n           ---------------------------- *\/\n        protected int contest(int b, int g, int r) {\n      \/* finds closest neuron (min dist) and updates freq *\/\n      \/* finds best neuron (min dist-bias) and returns position *\/\n      \/* for frequently chosen neurons, freq[i] is high and bias[i] is negative *\/\n      \/* bias[i] = gamma*((1\/netsize)-freq[i]) *\/\n            int i, dist, a, biasdist, betafreq;\n            int bestpos, bestbiaspos, bestd, bestbiasd;\n            int[] n;\n            bestd = ~(((int) 1) &lt;&lt; 31);\n            bestbiasd = bestd;\n            bestpos = -1;\n            bestbiaspos = bestpos;\n            for (i = 0; i &lt; netsize; i++) {\n                n = network[i];\n                dist = n[0] - b;\n                if (dist &lt; 0)\n                    dist = -dist;\n                a = n[1] - g;\n                if (a &lt; 0)\n                    a = -a;\n                dist += a;\n                a = n[2] - r;\n                if (a &lt; 0)\n                    a = -a;\n                dist += a;\n                if (dist &lt; bestd) {\n                    bestd = dist;\n                    bestpos = i;\n                }\n                biasdist = dist - ((bias[i]) &gt;&gt; (intbiasshift - netbiasshift));\n                if (biasdist &lt; bestbiasd) {\n                    bestbiasd = biasdist;\n                    bestbiaspos = i;\n                }\n                betafreq = (freq[i] &gt;&gt; betashift);\n                freq[i] -= betafreq;\n                bias[i] += (betafreq &lt;&lt; gammashift);\n            }\n            freq[bestpos] += beta;\n            bias[bestpos] -= betagamma;\n            return (bestbiaspos);\n        }\n    }\n<\/code><\/pre>\n<p><code>LZWEncoder.java<\/code>\u6e90\u7801\u5982\u4e0b\uff1a<\/p>\n<pre><code>    package com.yeetrack.selenium.Image;\n    import java.io.OutputStream;\n    import java.io.IOException;\n    \/\/==============================================================================\n    \/\/  Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott.\n    \/\/  K Weiner 12\/00\n    class LZWEncoder {\n        private static final int EOF = -1;\n        private int imgW, imgH;\n        private byte[] pixAry;\n        private int initCodeSize;\n        private int remaining;\n        private int curPixel;\n        \/\/ GIFCOMPR.C       - GIF Image compression routines\n        \/\/\n        \/\/ Lempel-Ziv compression based on 'compress'.  GIF modifications by\n        \/\/ David Rowley (mgardi@watdcsu.waterloo.edu)\n        \/\/ General DEFINEs\n        static final int BITS = 12;\n        static final int HSIZE = 5003; \/\/ 80% occupancy\n        \/\/ GIF Image compression - modified 'compress'\n        \/\/\n        \/\/ Based on: compress.c - File compression ala IEEE Computer, June 1984.\n        \/\/\n        \/\/ By Authors:  Spencer W. Thomas      (decvax!harpo!utah-cs!utah-gr!thomas)\n        \/\/              Jim McKie              (decvax!mcvax!jim)\n        \/\/              Steve Davies           (decvax!vax135!petsd!peora!srd)\n        \/\/              Ken Turkowski          (decvax!decwrl!turtlevax!ken)\n        \/\/              James A. Woods         (decvax!ihnp4!ames!jaw)\n        \/\/              Joe Orost              (decvax!vax135!petsd!joe)\n        int n_bits; \/\/ number of bits\/code\n        int maxbits = BITS; \/\/ user settable max # bits\/code\n        int maxcode; \/\/ maximum code, given n_bits\n        int maxmaxcode = 1 &lt;&lt; BITS; \/\/ should NEVER generate this code\n        int[] htab = new int[HSIZE];\n        int[] codetab = new int[HSIZE];\n        int hsize = HSIZE; \/\/ for dynamic table sizing\n        int free_ent = 0; \/\/ first unused entry\n        \/\/ block compression parameters -- after all codes are used up,\n        \/\/ and compression rate changes, start over.\n        boolean clear_flg = false;\n        \/\/ Algorithm:  use open addressing double hashing (no chaining) on the\n        \/\/ prefix code \/ next character combination.  We do a variant of Knuth's\n        \/\/ algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime\n        \/\/ secondary probe.  Here, the modular division first probe is gives way\n        \/\/ to a faster exclusive-or manipulation.  Also do block compression with\n        \/\/ an adaptive reset, whereby the code table is cleared when the compression\n        \/\/ ratio decreases, but after the table fills.  The variable-length output\n        \/\/ codes are re-sized at this point, and a special CLEAR code is generated\n        \/\/ for the decompressor.  Late addition:  construct the table according to\n        \/\/ file size for noticeable speed improvement on small files.  Please direct\n        \/\/ questions about this implementation to ames!jaw.\n        int g_init_bits;\n        int ClearCode;\n        int EOFCode;\n        \/\/ output\n        \/\/\n        \/\/ Output the given code.\n        \/\/ Inputs:\n        \/\/      code:   A n_bits-bit integer.  If == -1, then EOF.  This assumes\n        \/\/              that n_bits =&lt; wordsize - 1.\n        \/\/ Outputs:\n        \/\/      Outputs code to the file.\n        \/\/ Assumptions:\n        \/\/      Chars are 8 bits long.\n        \/\/ Algorithm:\n        \/\/      Maintain a BITS character long buffer (so that 8 codes will\n        \/\/ fit in it exactly).  Use the VAX insv instruction to insert each\n        \/\/ code in turn.  When the buffer fills up empty it and start over.\n        int cur_accum = 0;\n        int cur_bits = 0;\n        int masks[] =\n                {\n                        0x0000,\n                        0x0001,\n                        0x0003,\n                        0x0007,\n                        0x000F,\n                        0x001F,\n                        0x003F,\n                        0x007F,\n                        0x00FF,\n                        0x01FF,\n                        0x03FF,\n                        0x07FF,\n                        0x0FFF,\n                        0x1FFF,\n                        0x3FFF,\n                        0x7FFF,\n                        0xFFFF };\n        \/\/ Number of characters so far in this 'packet'\n        int a_count;\n        \/\/ Define the storage for the packet accumulator\n        byte[] accum = new byte[256];\n        \/\/----------------------------------------------------------------------------\n        LZWEncoder(int width, int height, byte[] pixels, int color_depth) {\n            imgW = width;\n            imgH = height;\n            pixAry = pixels;\n            initCodeSize = Math.max(2, color_depth);\n        }\n        \/\/ Add a character to the end of the current packet, and if it is 254\n        \/\/ characters, flush the packet to disk.\n        void char_out(byte c, OutputStream outs) throws IOException {\n            accum[a_count++] = c;\n            if (a_count &gt;= 254)\n                flush_char(outs);\n        }\n        \/\/ Clear out the hash table\n        \/\/ table clear for block compress\n        void cl_block(OutputStream outs) throws IOException {\n            cl_hash(hsize);\n            free_ent = ClearCode + 2;\n            clear_flg = true;\n            output(ClearCode, outs);\n        }\n        \/\/ reset code table\n        void cl_hash(int hsize) {\n            for (int i = 0; i &lt; hsize; ++i)\n                htab[i] = -1;\n        }\n        void compress(int init_bits, OutputStream outs) throws IOException {\n            int fcode;\n            int i \/* = 0 *\/;\n            int c;\n            int ent;\n            int disp;\n            int hsize_reg;\n            int hshift;\n            \/\/ Set up the globals:  g_init_bits - initial number of bits\n            g_init_bits = init_bits;\n            \/\/ Set up the necessary values\n            clear_flg = false;\n            n_bits = g_init_bits;\n            maxcode = MAXCODE(n_bits);\n            ClearCode = 1 &lt;&lt; (init_bits - 1);\n            EOFCode = ClearCode + 1;\n            free_ent = ClearCode + 2;\n            a_count = 0; \/\/ clear packet\n            ent = nextPixel();\n            hshift = 0;\n            for (fcode = hsize; fcode &lt; 65536; fcode *= 2)\n                ++hshift;\n            hshift = 8 - hshift; \/\/ set hash code range bound\n            hsize_reg = hsize;\n            cl_hash(hsize_reg); \/\/ clear hash table\n            output(ClearCode, outs);\n            outer_loop : while ((c = nextPixel()) != EOF) {\n                fcode = (c &lt;&lt; maxbits) + ent;\n                i = (c &lt;&lt; hshift) ^ ent; \/\/ xor hashing\n                if (htab[i] == fcode) {\n                    ent = codetab[i];\n                    continue;\n                } else if (htab[i] &gt;= 0) \/\/ non-empty slot\n                {\n                    disp = hsize_reg - i; \/\/ secondary hash (after G. Knott)\n                    if (i == 0)\n                        disp = 1;\n                    do {\n                        if ((i -= disp) &lt; 0)\n                            i += hsize_reg;\n                        if (htab[i] == fcode) {\n                            ent = codetab[i];\n                            continue outer_loop;\n                        }\n                    } while (htab[i] &gt;= 0);\n                }\n                output(ent, outs);\n                ent = c;\n                if (free_ent &lt; maxmaxcode) {\n                    codetab[i] = free_ent++; \/\/ code -&gt; hashtable\n                    htab[i] = fcode;\n                } else\n                    cl_block(outs);\n            }\n            \/\/ Put out the final code.\n            output(ent, outs);\n            output(EOFCode, outs);\n        }\n        \/\/----------------------------------------------------------------------------\n        void encode(OutputStream os) throws IOException {\n            os.write(initCodeSize); \/\/ write \"initial code size\" byte\n            remaining = imgW * imgH; \/\/ reset navigation variables\n            curPixel = 0;\n            compress(initCodeSize + 1, os); \/\/ compress and write the pixel data\n            os.write(0); \/\/ write block terminator\n        }\n        \/\/ Flush the packet to disk, and reset the accumulator\n        void flush_char(OutputStream outs) throws IOException {\n            if (a_count &gt; 0) {\n                outs.write(a_count);\n                outs.write(accum, 0, a_count);\n                a_count = 0;\n            }\n        }\n        final int MAXCODE(int n_bits) {\n            return (1 &lt;&lt; n_bits) - 1;\n        }\n        \/\/----------------------------------------------------------------------------\n        \/\/ Return the next pixel from the image\n        \/\/----------------------------------------------------------------------------\n        private int nextPixel() {\n            if (remaining == 0)\n                return EOF;\n            --remaining;\n            byte pix = pixAry[curPixel++];\n            return pix &amp; 0xff;\n        }\n        void output(int code, OutputStream outs) throws IOException {\n            cur_accum &amp;= masks[cur_bits];\n            if (cur_bits &gt; 0)\n                cur_accum |= (code &lt;&lt; cur_bits);\n            else\n                cur_accum = code;\n            cur_bits += n_bits;\n            while (cur_bits &gt;= 8) {\n                char_out((byte) (cur_accum &amp; 0xff), outs);\n                cur_accum &gt;&gt;= 8;\n                cur_bits -= 8;\n            }\n            \/\/ If the next entry is going to be too big for the code size,\n            \/\/ then increase it, if possible.\n            if (free_ent &gt; maxcode || clear_flg) {\n                if (clear_flg) {\n                    maxcode = MAXCODE(n_bits = g_init_bits);\n                    clear_flg = false;\n                } else {\n                    ++n_bits;\n                    if (n_bits == maxbits)\n                        maxcode = maxmaxcode;\n                    else\n                        maxcode = MAXCODE(n_bits);\n                }\n            }\n            if (code == EOFCode) {\n                \/\/ At EOF, write the rest of the buffer.\n                while (cur_bits &gt; 0) {\n                    char_out((byte) (cur_accum &amp; 0xff), outs);\n                    cur_accum &gt;&gt;= 8;\n                    cur_bits -= 8;\n                }\n                flush_char(outs);\n            }\n        }\n    }\n<\/code><\/pre>\n<p><code>AnimatedGifEncoder.java<\/code>\u6e90\u7801\u5982\u4e0b\uff1a<\/p>\n<pre><code>    package com.yeetrack.selenium.Image;\n    import java.io.*;\n    import java.awt.*;\n    import java.awt.image.*;\n    \/**\n     * Class AnimatedGifEncoder - Encodes a GIF file consisting of one or\n     * more frames.\n     * &lt;pre&gt;\n     * Example:\n     *    AnimatedGifEncoder e = new AnimatedGifEncoder();\n     *    e.start(outputFileName);\n     *    e.setDelay(1000);   \/\/ 1 frame per sec\n     *    e.addFrame(image1);\n     *    e.addFrame(image2);\n     *    e.finish();\n     * &lt;\/pre&gt;\n     * No copyright asserted on the source code of this class.  May be used\n     * for any purpose, however, refer to the Unisys LZW patent for restrictions\n     * on use of the associated LZWEncoder class.  Please forward any corrections\n     * to kweiner@fmsware.com.\n     *\n     * @author Kevin Weiner, FM Software\n     * @version 1.03 November 2003\n     *\n     *\/\n    public class AnimatedGifEncoder {\n        protected int width; \/\/ image size\n        protected int height;\n        protected Color transparent = null; \/\/ transparent color if given\n        protected int transIndex; \/\/ transparent index in color table\n        protected int repeat = -1; \/\/ no repeat\n        protected int delay = 0; \/\/ frame delay (hundredths)\n        protected boolean started = false; \/\/ ready to output frames\n        protected OutputStream out;\n        protected BufferedImage image; \/\/ current frame\n        protected byte[] pixels; \/\/ BGR byte array from frame\n        protected byte[] indexedPixels; \/\/ converted frame indexed to palette\n        protected int colorDepth; \/\/ number of bit planes\n        protected byte[] colorTab; \/\/ RGB palette\n        protected boolean[] usedEntry = new boolean[256]; \/\/ active palette entries\n        protected int palSize = 7; \/\/ color table size (bits-1)\n        protected int dispose = -1; \/\/ disposal code (-1 = use default)\n        protected boolean closeStream = false; \/\/ close stream when finished\n        protected boolean firstFrame = true;\n        protected boolean sizeSet = false; \/\/ if false, get size from first frame\n        protected int sample = 10; \/\/ default sample interval for quantizer\n        \/**\n         * Sets the delay time between each frame, or changes it\n         * for subsequent frames (applies to last frame added).\n         *\n         * @param ms int delay time in milliseconds\n         *\/\n        public void setDelay(int ms) {\n            delay = Math.round(ms \/ 10.0f);\n        }\n        \/**\n         * Sets the GIF frame disposal code for the last added frame\n         * and any subsequent frames.  Default is 0 if no transparent\n         * color has been set, otherwise 2.\n         * @param code int disposal code.\n         *\/\n        public void setDispose(int code) {\n            if (code &gt;= 0) {\n                dispose = code;\n            }\n        }\n        \/**\n         * Sets the number of times the set of GIF frames\n         * should be played.  Default is 1; 0 means play\n         * indefinitely.  Must be invoked before the first\n         * image is added.\n         *\n         * @param iter int number of iterations.\n         * @return\n         *\/\n        public void setRepeat(int iter) {\n            if (iter &gt;= 0) {\n                repeat = iter;\n            }\n        }\n        \/**\n         * Sets the transparent color for the last added frame\n         * and any subsequent frames.\n         * Since all colors are subject to modification\n         * in the quantization process, the color in the final\n         * palette for each frame closest to the given color\n         * becomes the transparent color for that frame.\n         * May be set to null to indicate no transparent color.\n         *\n         * @param c Color to be treated as transparent on display.\n         *\/\n        public void setTransparent(Color c) {\n            transparent = c;\n        }\n        \/**\n         * Adds next GIF frame.  The frame is not written immediately, but is\n         * actually deferred until the next frame is received so that timing\n         * data can be inserted.  Invoking &lt;code&gt;finish()&lt;\/code&gt; flushes all\n         * frames.  If &lt;code&gt;setSize&lt;\/code&gt; was not invoked, the size of the\n         * first image is used for all subsequent frames.\n         *\n         * @param im BufferedImage containing frame to write.\n         * @return true if successful.\n         *\/\n        public boolean addFrame(BufferedImage im) {\n            if ((im == null) || !started) {\n                return false;\n            }\n            boolean ok = true;\n            try {\n                if (!sizeSet) {\n                    \/\/ use first frame's size\n                    setSize(im.getWidth(), im.getHeight());\n                }\n                image = im;\n                getImagePixels(); \/\/ convert to correct format if necessary\n                analyzePixels(); \/\/ build color table &amp; map pixels\n                if (firstFrame) {\n                    writeLSD(); \/\/ logical screen descriptior\n                    writePalette(); \/\/ global color table\n                    if (repeat &gt;= 0) {\n                        \/\/ use NS app extension to indicate reps\n                        writeNetscapeExt();\n                    }\n                }\n                writeGraphicCtrlExt(); \/\/ write graphic control extension\n                writeImageDesc(); \/\/ image descriptor\n                if (!firstFrame) {\n                    writePalette(); \/\/ local color table\n                }\n                writePixels(); \/\/ encode and write pixel data\n                firstFrame = false;\n            } catch (IOException e) {\n                ok = false;\n            }\n            return ok;\n        }\n        \/**\n         * Flushes any pending data and closes output file.\n         * If writing to an OutputStream, the stream is not\n         * closed.\n         *\/\n        public boolean finish() {\n            if (!started) return false;\n            boolean ok = true;\n            started = false;\n            try {\n                out.write(0x3b); \/\/ gif trailer\n                out.flush();\n                if (closeStream) {\n                    out.close();\n                }\n            } catch (IOException e) {\n                ok = false;\n            }\n            \/\/ reset for subsequent use\n            transIndex = 0;\n            out = null;\n            image = null;\n            pixels = null;\n            indexedPixels = null;\n            colorTab = null;\n            closeStream = false;\n            firstFrame = true;\n            return ok;\n        }\n        \/**\n         * Sets frame rate in frames per second.  Equivalent to\n         * &lt;code&gt;setDelay(1000\/fps)&lt;\/code&gt;.\n         *\n         * @param fps float frame rate (frames per second)\n         *\/\n        public void setFrameRate(float fps) {\n            if (fps != 0f) {\n                delay = Math.round(100f \/ fps);\n            }\n        }\n        \/**\n         * Sets quality of color quantization (conversion of images\n         * to the maximum 256 colors allowed by the GIF specification).\n         * Lower values (minimum = 1) produce better colors, but slow\n         * processing significantly.  10 is the default, and produces\n         * good color mapping at reasonable speeds.  Values greater\n         * than 20 do not yield significant improvements in speed.\n         *\n         * @param quality int greater than 0.\n         * @return\n         *\/\n        public void setQuality(int quality) {\n            if (quality &lt; 1) quality = 1;\n            sample = quality;\n        }\n        \/**\n         * Sets the GIF frame size.  The default size is the\n         * size of the first frame added if this method is\n         * not invoked.\n         *\n         * @param w int frame width.\n         * @param h int frame width.\n         *\/\n        public void setSize(int w, int h) {\n            if (started &amp;&amp; !firstFrame) return;\n            width = w;\n            height = h;\n            if (width &lt; 1) width = 320;\n            if (height &lt; 1) height = 240;\n            sizeSet = true;\n        }\n        \/**\n         * Initiates GIF file creation on the given stream.  The stream\n         * is not closed automatically.\n         *\n         * @param os OutputStream on which GIF images are written.\n         * @return false if initial write failed.\n         *\/\n        public boolean start(OutputStream os) {\n            if (os == null) return false;\n            boolean ok = true;\n            closeStream = false;\n            out = os;\n            try {\n                writeString(\"GIF89a\"); \/\/ header\n            } catch (IOException e) {\n                ok = false;\n            }\n            return started = ok;\n        }\n        \/**\n         * Initiates writing of a GIF file with the specified name.\n         *\n         * @param file String containing output file name.\n         * @return false if open or initial write failed.\n         *\/\n        public boolean start(String file) {\n            boolean ok = true;\n            try {\n                out = new BufferedOutputStream(new FileOutputStream(file));\n                ok = start(out);\n                closeStream = true;\n            } catch (IOException e) {\n                ok = false;\n            }\n            return started = ok;\n        }\n        \/**\n         * Analyzes image colors and creates color map.\n         *\/\n        protected void analyzePixels() {\n            int len = pixels.length;\n            int nPix = len \/ 3;\n            indexedPixels = new byte[nPix];\n            NeuQuant nq = new NeuQuant(pixels, len, sample);\n            \/\/ initialize quantizer\n            colorTab = nq.process(); \/\/ create reduced palette\n            \/\/ convert map from BGR to RGB\n            for (int i = 0; i &lt; colorTab.length; i += 3) {\n                byte temp = colorTab[i];\n                colorTab[i] = colorTab[i + 2];\n                colorTab[i + 2] = temp;\n                usedEntry[i \/ 3] = false;\n            }\n            \/\/ map image pixels to new palette\n            int k = 0;\n            for (int i = 0; i &lt; nPix; i++) {\n                int index =\n                        nq.map(pixels[k++] &amp; 0xff,\n                                pixels[k++] &amp; 0xff,\n                                pixels[k++] &amp; 0xff);\n                usedEntry[index] = true;\n                indexedPixels[i] = (byte) index;\n            }\n            pixels = null;\n            colorDepth = 8;\n            palSize = 7;\n            \/\/ get closest match to transparent color if specified\n            if (transparent != null) {\n                transIndex = findClosest(transparent);\n            }\n        }\n        \/**\n         * Returns index of palette color closest to c\n         *\n         *\/\n        protected int findClosest(Color c) {\n            if (colorTab == null) return -1;\n            int r = c.getRed();\n            int g = c.getGreen();\n            int b = c.getBlue();\n            int minpos = 0;\n            int dmin = 256 * 256 * 256;\n            int len = colorTab.length;\n            for (int i = 0; i &lt; len;) {\n                int dr = r - (colorTab[i++] &amp; 0xff);\n                int dg = g - (colorTab[i++] &amp; 0xff);\n                int db = b - (colorTab[i] &amp; 0xff);\n                int d = dr * dr + dg * dg + db * db;\n                int index = i \/ 3;\n                if (usedEntry[index] &amp;&amp; (d &lt; dmin)) {\n                    dmin = d;\n                    minpos = index;\n                }\n                i++;\n            }\n            return minpos;\n        }\n        \/**\n         * Extracts image pixels into byte array \"pixels\"\n         *\/\n        protected void getImagePixels() {\n            int w = image.getWidth();\n            int h = image.getHeight();\n            int type = image.getType();\n            if ((w != width)\n                    || (h != height)\n                    || (type != BufferedImage.TYPE_3BYTE_BGR)) {\n                \/\/ create new image with right size\/format\n                BufferedImage temp =\n                        new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);\n                Graphics2D g = temp.createGraphics();\n                g.drawImage(image, 0, 0, null);\n                image = temp;\n            }\n            pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();\n        }\n        \/**\n         * Writes Graphic Control Extension\n         *\/\n        protected void writeGraphicCtrlExt() throws IOException {\n            out.write(0x21); \/\/ extension introducer\n            out.write(0xf9); \/\/ GCE label\n            out.write(4); \/\/ data block size\n            int transp, disp;\n            if (transparent == null) {\n                transp = 0;\n                disp = 0; \/\/ dispose = no action\n            } else {\n                transp = 1;\n                disp = 2; \/\/ force clear if using transparent color\n            }\n            if (dispose &gt;= 0) {\n                disp = dispose &amp; 7; \/\/ user override\n            }\n            disp &lt;&lt;= 2;\n            \/\/ packed fields\n            out.write(0 | \/\/ 1:3 reserved\n                    disp | \/\/ 4:6 disposal\n                    0 | \/\/ 7   user input - 0 = none\n                    transp); \/\/ 8   transparency flag\n            writeShort(delay); \/\/ delay x 1\/100 sec\n            out.write(transIndex); \/\/ transparent color index\n            out.write(0); \/\/ block terminator\n        }\n        \/**\n         * Writes Image Descriptor\n         *\/\n        protected void writeImageDesc() throws IOException {\n            out.write(0x2c); \/\/ image separator\n            writeShort(0); \/\/ image position x,y = 0,0\n            writeShort(0);\n            writeShort(width); \/\/ image size\n            writeShort(height);\n            \/\/ packed fields\n            if (firstFrame) {\n                \/\/ no LCT  - GCT is used for first (or only) frame\n                out.write(0);\n            } else {\n                \/\/ specify normal LCT\n                out.write(0x80 | \/\/ 1 local color table  1=yes\n                        0 | \/\/ 2 interlace - 0=no\n                        0 | \/\/ 3 sorted - 0=no\n                        0 | \/\/ 4-5 reserved\n                        palSize); \/\/ 6-8 size of color table\n            }\n        }\n        \/**\n         * Writes Logical Screen Descriptor\n         *\/\n        protected void writeLSD() throws IOException {\n            \/\/ logical screen size\n            writeShort(width);\n            writeShort(height);\n            \/\/ packed fields\n            out.write((0x80 | \/\/ 1   : global color table flag = 1 (gct used)\n                    0x70 | \/\/ 2-4 : color resolution = 7\n                    0x00 | \/\/ 5   : gct sort flag = 0\n                    palSize)); \/\/ 6-8 : gct size\n            out.write(0); \/\/ background color index\n            out.write(0); \/\/ pixel aspect ratio - assume 1:1\n        }\n        \/**\n         * Writes Netscape application extension to define\n         * repeat count.\n         *\/\n        protected void writeNetscapeExt() throws IOException {\n            out.write(0x21); \/\/ extension introducer\n            out.write(0xff); \/\/ app extension label\n            out.write(11); \/\/ block size\n            writeString(\"NETSCAPE\" + \"2.0\"); \/\/ app id + auth code\n            out.write(3); \/\/ sub-block size\n            out.write(1); \/\/ loop sub-block id\n            writeShort(repeat); \/\/ loop count (extra iterations, 0=repeat forever)\n            out.write(0); \/\/ block terminator\n        }\n        \/**\n         * Writes color table\n         *\/\n        protected void writePalette() throws IOException {\n            out.write(colorTab, 0, colorTab.length);\n            int n = (3 * 256) - colorTab.length;\n            for (int i = 0; i &lt; n; i++) {\n                out.write(0);\n            }\n        }\n        \/**\n         * Encodes and writes pixel data\n         *\/\n        protected void writePixels() throws IOException {\n            LZWEncoder encoder =\n                    new LZWEncoder(width, height, indexedPixels, colorDepth);\n            encoder.encode(out);\n        }\n        \/**\n         *    Write 16-bit value to output stream, LSB first\n         *\/\n        protected void writeShort(int value) throws IOException {\n            out.write(value &amp; 0xff);\n            out.write((value &gt;&gt; 8) &amp; 0xff);\n        }\n        \/**\n         * Writes string to output stream\n         *\/\n        protected void writeString(String s) throws IOException {\n            for (int i = 0; i &lt; s.length(); i++) {\n                out.write((byte) s.charAt(i));\n            }\n        }\n    }<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>\u5199selenium\u81ea\u52a8\u5316\u65f6\uff0c\u4e3a\u4e86\u67e5\u770b\u8fd0\u884c\u6548\u679c\uff0c\u540e\u7ed9\u6d4f\u89c8\u5668\u622a\u56fe\uff0c\u60f3\u5230\u53ef\u4ee5\u751f\u6210gif\u56fe\u7247\u6765\u5feb\u901f\u9884\u89c8\u3002\u770b\u5230\u5df2\u7ecf\u6709\u4eba\u5b9e\u73b0\u4e86\uff0c\u76f4\u63a5\u62ff\u8fc7\u6765\u3002\u4f5c\u8005\u662fKevin Weiner\u3002 \u5171\u6d89\u53ca\u5230\u4e09\u4e2ajava\u6587\u4ef6\uff0c\u5206\u522b\u662fNeu&#46;&#46;&#46;<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"pgc_sgb_lightbox_settings":"","footnotes":""},"categories":[33],"tags":[8,50,7,3,5,42,41],"class_list":["post-943","post","type-post","status-publish","format-standard","hentry","category-coding","tag-java","tag-selenium","tag-7","tag-3","tag-5","tag-42","tag-41"],"views":4923,"_links":{"self":[{"href":"https:\/\/www.yeetrack.com\/index.php?rest_route=\/wp\/v2\/posts\/943","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.yeetrack.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.yeetrack.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.yeetrack.com\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.yeetrack.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=943"}],"version-history":[{"count":3,"href":"https:\/\/www.yeetrack.com\/index.php?rest_route=\/wp\/v2\/posts\/943\/revisions"}],"predecessor-version":[{"id":1512,"href":"https:\/\/www.yeetrack.com\/index.php?rest_route=\/wp\/v2\/posts\/943\/revisions\/1512"}],"wp:attachment":[{"href":"https:\/\/www.yeetrack.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=943"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.yeetrack.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=943"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.yeetrack.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=943"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}