当前位置: 代码迷 >> Android >> android gif view 显示图片(网络源码批改)
  详细解决方案

android gif view 显示图片(网络源码批改)

热度:401   发布时间:2016-05-01 19:32:10.0
android gif view 显示图片(网络源码修改)
gif的解析,显示,在google上有一位作者开放了源码,于是我也下载研究了下,的确可用, 不过些许问题,如,解析大一些的文件就会出现oome。

我修改了下源码,模拟器上会因为图片过大无法解析。一张gif2.07m,解析完全 会有179帧,如果用png,100质量存储总有4.2m,在手机上只能解析128帧,其它因为内存不足无法解析,所以还是没有优化好。但是其它的图片到现在多数都解析成功了 ,有些不只是大小的问题,还关系到每帧高宽。3m+的gif图片解析也可以,但是那张2m的就不行。

废话不多了,现在介绍我的修改过程。
首先,原作者的地址为:http://code.google.com/p/gifview/
我只说我修改的部分:
先看decoder:
private Bitmap image; // current frame
    private Bitmap lastImage; // previous frame
一个是存储当前的帧,一个存储上一帧.
为什么要存储上一帧呢?我也不大懂,猜是因为上一帧与当前的帧关联大,许多地方可以利用的.

从作者的代码来看,它提供了即时解析,解析一帧显示一帧.但是我考虑到,一张gif图片可能不是只从头到尾看一次,用户可能会想像pc的浏览器那样,动画完成后再从头开始,所以,我就把这些解析的帧存储在一个list里,解析完成后把list传到view中,然后就用线程处理画图的问题.

readContents()是读取内容的主要方法.其中的readImage就是读取图片了.而其它的方法读一些其它信息.我对gif了解不太多,也不能说出每个方法具体干什么的.
看原来的
private void readImage() {        Log.d(TAG, "readImage.");        ix=readShort(); // (sub)image position & size        iy=readShort();        iw=readShort();        ih=readShort();        int packed=read();        lctFlag=(packed&0x80)!=0; // 1 - local color table flag        interlace=(packed&0x40)!=0; // 2 - interlace flag        // 3 - sort flag        // 4-5 - reserved        lctSize=2<<(packed&7); // 6-8 - local color table size        if (lctFlag) {            lct=readColorTable(lctSize); // read table            act=lct; // make local table active        } else {            act=gct; // make global table active            if (bgIndex==transIndex) {                bgColor=0;            }        }        int save=0;        if (transparency) {            save=act[transIndex];            act[transIndex]=0; // set transparent color if specified        }        if (act==null) {            status=STATUS_FORMAT_ERROR; // no color table defined        }        if (err()) {            return;        }        decodeImageData(); // decode pixel data        skip();        if (err()) {            return;        }        frameCount++;        // create new image to receive frame data        image=Bitmap.createBitmap(width,height,Config.ARGB_4444);        // createImage(width, height);        setPixels(); // transfer pixel data to image        if (gifFrame==null) {            gifFrame=new GifFrame(image,delay);            currentFrame=gifFrame;        } else {            GifFrame f=gifFrame;            while (f.nextFrame!=null) {                f=f.nextFrame;            }            f.nextFrame=new GifFrame(image,delay);        }        // frames.addElement(new GifFrame(image, delay)); // add image to frame        // list        if (transparency) {            act[transIndex]=save;        }        resetFrame();        action.parseOk(true,frameCount);    }可以看到image,这些建一个图,图片的生成是在setPixels里面的.这个方法做的事比较多了.private void setPixels() {        Log.d(TAG, "setPixels.");        int[] dest=new int[width*height];建一个颜色数组.然后把它填充,就可以画出一张图片了.里面的if (lastImage!=null) {                lastImage.getPixels(dest,0,width,0,0,width,height);                // copy pixels                if (lastDispose==2) {                    // fill last image rect area with background color                    int c=0;                    if (!transparency) {                        c=lastBgColor;                    }                    for (int i=0;i<lrh;i++) {                        int n1=(lry+i)*width+lrx;                        int n2=n1+lrw;                        for (int k=n1;k<n2;k++) {                            dest[k]=c;                        }                    }                }            }就是利用了上一帧的内容了.然后再设置新的内容:if (line<height) {                int k=line*width;                int dx=k+ix; // start of line in dest                int dlim=dx+iw; // end of dest line                if ((k+width)<dlim) {                    dlim=k+width; // past dest edge                }                int sx=i*iw; // start of line in source                while (dx<dlim) {                    // map color and insert in destination                    int index=((int)pixels[sx++])&0xff;                    int c=act[index];                    if (c!=0) {                        dest[dx]=c;                    }                    dx++;                }            }最后创建 图片:image=Bitmap.createBitmap(dest,width,height,Config.ARGB_4444);然后它的帧是像c++那样,链接的,当前的帧有一个指针指向下一帧:public GifFrame nextFrame;这样,解析完成后可以从第一帧开始,第一帧指针第二帧,第二帧指向第三帧.这样一直下去.我觉得还不如把这些帧存储在一个list里,按顺序加入,按顺序显示就可以了.于是做了些小改动:image=Bitmap.createBitmap(dest, width, height, Config.ARGB_4444);        frame2ArrayList.add(new GifFrame(image, delay));加入到列表中.而GifFrame也只剩下了bitmap,delay.两个值 了.建一个存放帧的列表:ArrayList<GifFrame> frame2ArrayList;//存放解析的帧。如果文件不是太大,解析是没有问题的.但是内存消耗较大的是setPixels方法.如果把这个方法再优化,就可以解析更大的文件了.在解析完成后回调view的方法:@Override    public void parseOk(boolean parseStatus, int frameIndex) {        Log.d(TAG, "parseOk.frameIndex:"+frameIndex);        decodeFinish(parseStatus, frameIndex);    }private void decodeFinish(boolean parseStatus, int frameIndex) {        if (!parseStatus) {            Log.d(TAG, "解析失败。");            if (null!=imageLoadCallback) {                imageLoadCallback.loadError();            }            return;        }        if (gifDecoder==null) {            /*if (null!=imageLoadCallback) {                   imageLoadCallback.loadError();               }*/            return;        }        gifFrames=gifDecoder.getFrameArrayList();        currImageIdx=0;        frameLength=gifFrames.size();        //if (rect==null) {        mHandler.post(new Runnable() {            @Override            public void run() {                if (null!=imageLoadCallback) {                    imageLoadCallback.loadFinish();                }                                Bitmap bitmap=gifFrames.get(0).image;                Log.d(TAG, "gif帧间隔为:"+gifFrames.get(0).delay);                setShowDimension(bitmap.getWidth(), bitmap.getHeight());            }        });        //}        gifDecoder.free();        gifDecoder=null;        System.gc();        startAnimate();    }private void startAnimate() {        Log.d(TAG, "startAnimate.animationType:"+animationType);        switch (animationType) {            case ANIMATION:                Log.d(TAG, "ANIMATION.");                if (frameLength>1) {                    if (drawThread==null) {                        drawThread=new DrawThread();                    } else {                        drawThread.interrupt();                        drawThread=new DrawThread();                    }                    drawThread.start();                } else if (frameLength==1) {                    reDraw();                }                break;}画图:private class DrawThread extends Thread {        @Override        public void run() {            //Log.d(TAG, "DrawThread.run.");            if (gifFrames==null||frameLength<1) {                return;            }            while (isRun) {                GifFrame frame=gifFrames.get(currImageIdx++);                if (currImageIdx>=frameLength) {                    currImageIdx=0;//重新播放。                    //break;                }                currentImage=frame.image;                if (pause==false) {                    long delay=frame.delay;                    //Log.d(TAG, "run.currentImage:"+currentImage+" pause:"+pause+" isRun:"+isRun+" delay:"+delay);                    Message msg=mHandler.obtainMessage();                    mHandler.sendMessage(msg);                    SystemClock.sleep(delay);                } else {                    SystemClock.sleep(10);                    break;                }            }            Log.d(TAG, "finish run.");        }    }由于view在销毁时没有停止这个DrawThread线程,所以需要设置isRun与pause.public void stopAnimate() {        Log.d(TAG, "stopAnimate.");        isRun=false;        pause=true;}供外部调用.这样看上去与原来的解析几乎没有差别,但是后来看到Android文档,说drawbitmap方法,中有一个参数为int[]类型的方法,说是会比较快,然后就修改了解析 的方法,返回的不再是Bitmap了,快了一些.因为生成Bitmap会消耗一些资源,慢一点.于是setPixels方法修改为:private void setPixels() {        try {            int[] dest=new int[width*height];//这里申请内存会溢出。            // fill in starting image contents based on last image's dispose code            if (lastDispose>0) {                if (lastDispose==3) {                    // use image before last                    int n=frameCount-2;                    if (n>0) {                        lastColors=getColorFrame(n-1);                    } else {                        lastColors = null;                    }                }                //Log.d(TAG, "lastColors:"+lastColors+" lastDispose:"+lastDispose+" n:"+frameCount);                if (lastColors!=null) {                    //dest=lastColors.colors;                                        System.arraycopy(lastColors.colors, 0, dest, 0, lastColors.colors.length);                                        // copy pixels                    if (lastDispose==2) {                        // fill last image rect area with background color                        int c=0;                        if (!transparency) {                            c=lastBgColor;                        }                        for (int i=0; i<lrh; i++) {                            int n1=(lry+i)*width+lrx;                            int n2=n1+lrw;                            for (int k=n1; k<n2; k++) {                                dest[k]=c;                            }                        }                    }                }            }            /*if (dest==null) {                dest=new int[width*height];            }*/            // copy each source line to the appropriate place in the destination            int pass=1;            int inc=8;            int iline=0;            for (int i=0; i<ih; i++) {                int line=i;                if (interlace) {                    if (iline>=ih) {                        pass++;                        switch (pass) {                            case 2:                                iline=4;                                break;                            case 3:                                iline=2;                                inc=4;                                break;                            case 4:                                iline=1;                                inc=2;                        }                    }                    line=iline;                    iline+=inc;                }                                line+=iy;                if (line<height) {                    int k=line*width;                    int dx=k+ix; // start of line in dest                    int dlim=dx+iw; // end of dest line                    if ((k+width)<dlim) {                        dlim=k+width; // past dest edge                    }                    int sx=i*iw; // start of line in source                    while (dx<dlim) {                        // map color and insert in destination                        int index=((int) pixels[sx++])&0xff;                        int c=act[index];                        if (c!=0) {                            dest[dx]=c;                        }                        dx++;                    }                }            }            lastColors=new ColorsFrame(dest, delay, width, height, transparency);            colorsArr.add(lastColors);        } catch (OutOfMemoryError e) {            Log.d(TAG, "colorsArr.size():"+colorsArr.size()+"-"+colorsArr.get(0).colors.length);            status=STATUS_FINISH;            e.printStackTrace();        }    }class ColorsFrame {        private ColorsFrame(int[] colors, int delay, int width, int height,boolean transparency) {            this.colors=colors;            this.delay=delay;            this.width=width;            this.height=height;            this.transparency=transparency;        }        int[] colors;        int delay;        int width;        int height;        boolean transparency;这些属性用于画图的.    }返回的同样是一个列表,但这次不是Bitmap,而是int[] colors;gif解析就是比较耗资源的过程.而且很恶心的是readNetscapeExt这个方法,do {            readBlock();            if (block[0]==1) {                // loop count sub-block                int b1=((int) block[1])&0xff;                int b2=((int) block[2])&0xff;                loopCount=(b2<<8)|b1;            }        } while ((blockSize>0)&&!err());有时会一直运行,因为while里的条件一直没有达到.换了以后,gif的解析在内存没有溢出的情况下,会快一些.然后就是view里的decodeFinish();gifFrames=gifDecoder2.getFrameArrayList();        currImageIdx=0;        frameLength=gifFrames.size();        //if (rect==null) {        mHandler.post(new Runnable() {            @Override            public void run() {                if (null!=imageLoadCallback) {                    imageLoadCallback.loadFinish();                }                                //Bitmap bitmap=gifFrames.get(0).image;                int width=gifFrames.get(0).width;                int height=gifFrames.get(0).height;                Log.d(TAG, "gif帧间隔为:"+gifFrames.get(0).delay);                setShowDimension(width, height);            }        });在onDraw里把它画出来:canvas.drawBitmap(currentImage.colors, 0, currentImage.width, 0, 0, currentImage.width,                currentImage.height, currentImage.transparency, null);=====================由于Android里面的内存限制,大一些的文件不容易解析(解析的过程优化得不够,本人能力有限,只想到从生成Bitmap到直接使用int[]来画图).所以如果用c++解析再把结果传回来,性能上会好些.看作者的GifFrame,觉得他是从c++中转化来的代码.

完整的代码暂时不放上来了,可以参考原作者的代码,然后如果需要修改为int[]类型的值,传回去播放,最后的效果,可以从本人的微博程序中看到.

应网友要求把全部完整代码放上.但是上面说的进一步处理画图没有包含在内,我认为,如果你想得到一些什么,总是要付出的吧,像散架的汽车就等着组合 了,,code.google上已经上传了,在这里看看自己修改吧.性能影响不会太多

2012.4.17 gif.zip 完整的工程,在/sdcard/中放置test.gif就可以了,
最近又发现一个c写的lib,gif2png,解码部分可以拿出来,由于我对图像处理不熟悉,只能转换部分,虽然读取了整个gif文件,且解码了,但不知道如何将这些rgb值与一些char*等转为需要的像素值。

之前还有一个gifscile这是另外的解码方式,同样解码后不知道如何转为像素值。而且免费,随意使用。

现在把代码放上来,如果会转换的童鞋,可以帮忙弄下,工程里面已经相对完整了。Hello.zip里的jni目录gifread.c是读取gif的文件,/sdcard/test.gif必须


1 楼 717693247 2012-03-27  
楼主:这篇文章我初略的看了一下,但是好像还是不能解决解析大一点的gif文件文件哦?比如说一个gif的某一帧太大,或者是这个gif的帧太多的话!就有可能会报OOM了!最近我也在看这个GifDecoder,始终都是把每帧都存到了内存中!要是内存中只存一帧,在该显下一帧的时候能从文件中去现读,然后把当前帧recycle()一下,这样的话,应该不会报OOM了,只不过这不一定哈,要是这一帧太大的话,可能还是会报!还有一个问题就是从文件中去现读的话,效率肯定又跟不上了!真的是很难找到一个两全齐美的办法啊!要是楼主你现在有什么好的办法,还望再分享一下!先谢谢了!
最后向楼主您致敬!
2 楼 717693247 2012-03-27  
楼主:这篇文章我初略的看了一下,但是好像还是不能解决解析大一点的gif文件文件哦?比如说一个gif的某一帧太大,或者是这个gif的帧太多的话!就有可能会报OOM了!最近我也在看这个GifDecoder,始终都是把每帧都存到了内存中!要是内存中只存一帧,在该显下一帧的时候能从文件中去现读,然后把当前帧recycle()一下,这样的话,应该不会报OOM了,只不过这不一定哈,要是这一帧太大的话,可能还是会报!还有一个问题就是从文件中去现读的话,效率肯定又跟不上了!真的是很难找到一个两全齐美的办法啊!要是楼主你现在有什么好的办法,还望再分享一下!先谢谢了!
最后向楼主您致敬!
3 楼 717693247 2012-03-27  
楼主:这篇文章我初略的看了一下,但是好像还是不能解决解析大一点的gif文件文件哦?比如说一个gif的某一帧太大,或者是这个gif的帧太多的话!就有可能会报OOM了!最近我也在看这个GifDecoder,始终都是把每帧都存到了内存中!要是内存中只存一帧,在该显下一帧的时候能从文件中去现读,然后把当前帧recycle()一下,这样的话,应该不会报OOM了,只不过这不一定哈,要是这一帧太大的话,可能还是会报!还有一个问题就是从文件中去现读的话,效率肯定又跟不上了!真的是很难找到一个两全齐美的办法啊!要是楼主你现在有什么好的办法,还望再分享一下!先谢谢了!
最后向楼主您致敬!
4 楼 717693247 2012-03-27  
不好意思,感觉这个submit按钮有问题,提交了三次! 望见谅
5 楼 phenom 2012-03-28  
oome暂时没有想到什么办法解决,而且文章里也说到了。
如果解析一帧再放到文件中,再解析下一帧会慢很多,机器好一些就不用太担心内存的问题,i9000解析gif时,较少会出现内存不够用,解析会消耗较多的内存是正常的,gif的标号太多时,会出现问题较常。

其实我一直在想用c来解析,可惜现在都忘了,不熟悉,还有jni,最近在努力学习c,jni这方面的,争取早日解决内存的问题。

上面这段代码后来我在git上看到是一个老外写的,如果原创是他,我觉得googlecode上作者应该注明。

  相关解决方案