1.概述
在深入了解Apng动画播放之前,我们需要对Apng的结构有所了解,具体参见Apng动画介绍,对Apng的整体结构有所了解后,下面我们来讲讲Apng动画的播放,主要包括Apng解析和Apng渲染两个过程。
2. Apng动画播放流程
Apng动画播放流程包括Apng解析和Apng渲染两个过程,Apng解析主要有两种方法,下面我们将会介绍,而Apng渲染主要包括三个步骤:消除(dispose)、合成(blend)、绘制(draw),由此得到Apng动画播放流程图如下:
Apng动画播放流程
3. Apng的解析
Apng的解析主要是将Apng文件转化成Apng序列帧Frame-n,从上面的流程图可知,Apng文件的解析列出了两种方案,下面来分别说说:
1)Apng文件首先经过一个解压(ApngExact)的过程,生成png序列帧保存在本地,然后经过加载(LoadPng)处理生成序列帧Frame-n。假设Apng动画文件总共有90帧,那么经过ApngExact处理后,会生成90张png序列帧保存在本地,每帧通过LoadPng处理生成Bitmap并供后面的Apng渲染使用。
2)Apng是一个独立的文件,我们自己编写读取Apng文件的代码类:ApngReader,当渲染第i帧时,通过ApngReader直接获取第i帧的Bitmap。
比较:
1)方案一是将Apng文件全部解压成png序列图片保存在本地,方案二是把Apng文件当做一个整体去处理,需要第几帧直接读取第几帧,并将该帧以Bitmap的形似保存到内存。
2)方案一解压得到的png图片在后面的渲染中需要转化成Bitamp,而方案二直接就获取了第几帧的Bitmap,相比于方案一,方案二减少了一个从SD卡读取png文件的操作。
4. Apng的渲染
方案一的具体实现大家可以参考github上面的一个项目apng-view,下面我们来讲讲方案二的具体实现,即ApngReader的具体实现。
1) 解析Apng的每一帧我们是将整个文件放到一个buffer里面,并且通过RandomAccessFile、MappedByteBuffer来读取Apng的每一帧,ApngReader的构造函数如下:
publicApngReader(StringapngFile)throwsIOException,FormatNotSupportException{RandomAccessFilef=newRandomAccessFile(apngFile,"r");mBuffer=f.getChannel().map(FileChannel.MapMode.READ_ONLY,0,f.length());f.close();if(mBuffer.getInt()!=PNG_SIG&&mBuffer.getInt(4)!=PNG_SIG_VER&&mBuffer.getInt(8)!=CODE_IHDR){thrownewFormatNotSupportException("Notapng/apngfile");}mChunk=newApngMmapParserChunk(mBuffer);reset();}
下面来看看读取每一帧的方法:
/***getnextframecontrolinfo&bitmap**@returnnextframecontrolinfo,ornullifnonextFCTLchunk||nonextIDAT/FDAT*@throwsIOException*/publicApngFramenextFrame()throwsIOException{//resetreadpointersfrompreviousframe'slockmPngStream.clearDataChunks();mPngStream.resetPos();mChunk.unlockRead();//locatenextFCTLchunkbooleanihdrCopied=false;while(mChunk.typeCode!=CODE_fcTL){switch(mChunk.typeCode){caseCODE_IEND:returnnull;caseCODE_IHDR:mPngStream.setIHDR(mChunk.duplicateData());break;caseCODE_acTL:handleACTL(mChunk);ihdrCopied=true;break;default:handleOtherChunk(mChunk);}mChunk.parseNext();}//locatedatFCTLchunkApngFrameframe=newApngFrame();mChunk.assignTo(frame);//locatenextIDATorfdAtchunkmChunk.parseNext();//firstmovenextfromcurrentFCTLwhile(mChunk.typeCode!=CODE_IDAT&&mChunk.typeCode!=CODE_fdAT){switch(mChunk.typeCode){caseCODE_IEND:returnnull;caseCODE_IHDR:mPngStream.setIHDR(mChunk.duplicateData());ihdrCopied=true;break;caseCODE_acTL:handleACTL(mChunk);break;default:handleOtherChunk(mChunk);}mChunk.parseNext();}//locatedatfirstIDATorfdATchunk//collectallconsecutivedatchunksbooleanneedUpdateIHDR=true;intdataOffset=mChunk.getOffset();while(mChunk.typeCode==CODE_fdAT||mChunk.typeCode==CODE_IDAT){if(needUpdateIHDR&&(!ihdrCopied||mChunk.typeCode==CODE_fdAT)){mPngStream.updateIHDR(frame.getWidth(),frame.getHeight());needUpdateIHDR=false;}if(mChunk.typeCode==CODE_fdAT){mPngStream.addDataChunk(newFdat2IdatChunk(mChunk));}else{mPngStream.addDataChunk(newApngMmapParserChunk(mChunk));}mChunk.parseNext();}//lockpositionforthisframe'simageasOutputStreammChunk.lockRead(dataOffset);frame.imageStream=mPngStream;returnframe;}
2) Apng的消除操作Apng的消除操作是在ApngFrameRender的render方法做的,方法如下:
/***渲染当前帧画面**@paramframeapng中当前帧*@return渲染合成后的当前帧图像*/publicBitmaprender(ApngFrameframe,BitmapframeBmp){//执行消除操作dispose(frame);//合成当前帧blend(frame,frameBmp);returnmRenderFrame;}
dispose(ApngFrame frame)方法如下:
/***帧图像析构消除-提交结果*/privatevoiddispose(ApngFrameframe){//lastframedisposeopswitch(mLastDisposeOp){caseAPNG_DISPOSE_OP_NONE://noopbreak;caseAPNG_DISPOSE_OP_BACKGROUND://clearrectmRenderCanvas.clipRect(mDisposeRect);mRenderCanvas.drawColor(Color.TRANSPARENT,PorterDuff.Mode.CLEAR);mRenderCanvas.clipRect(mFullRect,Region.Op.REPLACE);break;caseAPNG_DISPOSE_OP_PREVIOUS://swapworkandcachebitmapBitmapbmp=mRenderFrame;mRenderFrame=mDisposedFrame;mDisposedFrame=bmp;mRenderCanvas.setBitmap(mRenderFrame);mDisposeCanvas.setBitmap(mDisposedFrame);break;}//currentframedisposeopmLastDisposeOp=frame.getDisposeOp();switch(mLastDisposeOp){caseAPNG_DISPOSE_OP_NONE://noopbreak;caseAPNG_DISPOSE_OP_BACKGROUND://cacherectfornextcleardisposeintx=frame.getxOff();inty=frame.getyOff();mDisposeRect.set(x,y,x+frame.getWidth(),y+frame.getHeight());break;caseAPNG_DISPOSE_OP_PREVIOUS://cachebmpfornextrestoredisposemDisposeCanvas.clipRect(mFullRect,Region.Op.REPLACE);mDisposeCanvas.drawColor(Color.TRANSPARENT,PorterDuff.Mode.CLEAR);mDisposeCanvas.drawBitmap(mRenderFrame,0,0,null);break;}}
3) Apng的合成操作Apng的合成操作是在每一帧经过dispose之后做的,具体方法是blend(ApngFrame frame, Bitmap frameBmp),代码如下:
/***帧图像合成*/privatevoidblend(ApngFrameframe,BitmapframeBmp){intxOff=frame.getxOff();intyOff=frame.getyOff();mRenderCanvas.clipRect(xOff,yOff,xOff+frame.getWidth(),yOff+frame.getHeight());if(frame.getBlendOp()==APNG_BLEND_OP_SOURCE){mRenderCanvas.drawColor(Color.TRANSPARENT,PorterDuff.Mode.CLEAR);}mRenderCanvas.drawBitmap(frameBmp,xOff,yOff,null);mRenderCanvas.clipRect(mFullRect,Region.Op.REPLACE);}
4) Apng的绘制Apng的每一帧经过消除、合成操作之后,就可以在View上面draw,具体代码如下:
/***drawtheappointedframe*/privatevoiddrawFrame(AnimParamsanimItem,ApngFrameframe,BitmapframeBmp){if(surfaceEnabled&&!isInterrupted()){//starttodrawtheframetry{Matrixmatrix=newMatrix();matrix.setScale(mScale,mScale);Bitmapbmp=mFrameRender.render(frame,frameBmp);//saveBitmap(bmp,index);index++;Canvascanvas=getHolder().lockCanvas();//anti-aliasingcanvas.drawColor(Color.TRANSPARENT,PorterDuff.Mode.CLEAR);float[]tranLeftAndTop=ApngUtils.getTranLeftAndTop(canvas,bmp,animItem.align,mScale,animItem.percent);canvas.setDrawFilter(newPaintFlagsDrawFilter(0,Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG));matrix.postTranslate(tranLeftAndTop[0],tranLeftAndTop[1]);canvas.drawBitmap(bmp,matrix,null);getHolder().unlockCanvasAndPost(canvas);//unlockthecanvas}catch(Exceptione){Log.e(TAG,"drawerrormsg:"+Log.getStackTraceString(e));}}}实例我们是在SurfaceView上面来绘制Apng的每一帧,例子如下:
Activity代码:
publicclassMainActivityextendsActivity{privateApngSurfaceViewmApngSurfaceView;privatestaticfinalStringCOLOR_BALL_IMAGE_PATH="assets://color_ball.png";//privatestaticfinalStringCAR_IMAGE_PATH="assets://car.png";@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mApngSurfaceView=(ApngSurfaceView)findViewById(R.id.apng_surface_view);ButtonstartPlay=(Button)findViewById(R.id.start_play);startPlay.setOnClickListener(newView.OnClickListener(){@OverridepublicvoidonClick(Viewv){playAnim();}});}privatevoidplayAnim(){Filefile=FileUtils.processApngFile(COLOR_BALL_IMAGE_PATH,this);if(file==null)return;AnimParamsanimItem=newAnimParams();animItem.align=2;animItem.imagePath=file.getAbsolutePath();animItem.isHasBackground=true;animItem.percent=0.5f;mApngSurfaceView.addApngForPlay(animItem);}}
Layout代码:
<" >