Android高仿抖音照片电影功能

作者: yellowcath | 来源:发表于2018-09-12 17:44 被阅读69次

    PhotoMovie(https://github.com/yellowcath/PhotoMovie)可轻松实现类似抖音、微视、美拍的照片电影功能。效果如下

    滤镜效果

    filter.gif

    转场效果

    transfer.gif

    基本用法

    可参照DemoPresenter

            //添加图片
            List<PhotoData> photoDataList = new LinkedList<PhotoData>();
            photoDataList.add(new SimplePhotoData(context,photoPath1,PhotoData.STATE_LOCAL));
            ...
            photoDataList.add(new SimplePhotoData(context,photoPathN,PhotoData.STATE_LOCAL));
            //生成图片源
            PhotoSource photoSource = new PhotoSource(photoDataList);
            //生成照片电影(使用预定义的水平转场动画)
            PhotoMovie photoMovie = PhotoMovieFactory.generatePhotoMovie(photoSource, PhotoMovieFactory.PhotoMovieType.HORIZONTAL_TRANS);
            //生成负责绘制电影内容的MovieRenderer
            MovieRenderer movieRenderer = new GLTextureMovieRender(glTextureView);
            /**
             * OR  MovieRenderer movieRenderer = new GLSurfaceMovieRenderer(glSurfaceView);
             */
            //照片电影播放器
            PhotoMoviePlayer photoMoviePlayer = new PhotoMoviePlayer(context);
            photoMoviePlayer.setMovieRenderer(mMovieRenderer);
            photoMoviePlayer.setMovieListener(...);
            photoMoviePlayer.setLoop(true);
            photoMoviePlayer.setOnPreparedListener(new PhotoMoviePlayer.OnPreparedListener() {
                @Override
                public void onPreparing(PhotoMoviePlayer moviePlayer, float progress) {
                }
    
                @Override
                public void onPrepared(PhotoMoviePlayer moviePlayer, int prepared, int total) {
                     mPhotoMoviePlayer.start();
                }
    
                @Override
                public void onError(PhotoMoviePlayer moviePlayer) {
                }
            });
            photoMoviePlayer.prepare();
    

    轻松扩展

    PhotoMovie使用模块化的设计,每个部分都可以自定义然后替换,主要类图如下
    [图片上传失败...(image-9d4f2d-1536745039476)]

    • MovieSegment:电影片段,每个电影片段都有特定的时长,在这段时间之内以特定的方式播放图片,例如ScaleSegment会对图片做缩放动画、EndGaussianBlurSegment会对图片做从清晰到模糊的高斯模糊动画

    • PhotoMovie:核心类,代表照片电影本身,由图片源(PhotoSource)和若干电影片段(MovieSegment)组成一个完整的照片电影,图片通过PhotoAllocator分配给MovieSegment

    • MovieLayer:为MovieSegment扩展绘制多层特效的功能,例如SubtitleLayer提供字幕展示

    • IMovieFilter:为整个照片电影提供滤镜

    • MovieRenderer:负责把照片电影渲染到指定的输出界面,例如TextureView(GLTextureMovieRender)、GLSurfaceView(GLSurfaceMovieRenderer)

    • PhotoMoviePlayer:提供类似MediaPlayer的接口,负责播放照片电影,播放进度由IMovieTimer控制

    扩展电影类型

    目前内置了6种类型,后两种即是抖音的左右切换和上下切换,Thaw和WINDOW仿自美拍

     public enum PhotoMovieType {
            THAW,  //融雪
            SCALE, //缩放
            SCALE_TRANS, //缩放 & 平移
            WINDOW, //窗扉
            HORIZONTAL_TRANS,//横向平移
            VERTICAL_TRANS//纵向平移
        }
    

    这里以微视的渐变特效为例展示如何扩展
    分析得出,渐变特效首先图片居中放置,然后全程做一个微弱的放大动画,后半部分同时透明度变化消失
    ,更直观的流程如下图
    [图片上传失败...(image-2d1b15-1536745039476)]
    可见需要两个不同的片段类型
    首先创建FitCenterScaleSegment,继承FitCenterSegment,实现单张图片的放大动画

    public class FitCenterScaleSegment extends FitCenterSegment {
        /**
         * 缩放动画范围
         */
        private float mScaleFrom;
        private float mScaleTo;
    
        private float mProgress;
    
        /**
         * @param duration  片段时长
         * @param scaleFrom 缩放范围
         * @param scaleTo   缩放范围
         */
        public FitCenterScaleSegment(int duration, float scaleFrom, float scaleTo) {
            super(duration);
            mScaleFrom = scaleFrom;
            mScaleTo = scaleTo;
        }
    
        @Override
        protected void onDataPrepared() {
            super.onDataPrepared();
        }
    
        @Override
        public void drawFrame(GLESCanvas canvas, float segmentProgress) {
            mProgress = segmentProgress;
            if (!mDataPrepared) {
                return;
            }
            drawBackground(canvas);
            float scale = mScaleFrom + (mScaleTo - mScaleFrom) * mProgress;
            //FitCenterSegment已经具有缩放能力,这里传缩放值即可
            drawContent(canvas, scale);
        }
        //提升这两个函数的访问权限,供转场时使用
            @Override
        public void drawContent(GLESCanvas canvas, float scale) {
            super.drawContent(canvas, scale);
        }
    
        @Override
        public void drawBackground(GLESCanvas canvas) {
            super.drawBackground(canvas);
        }
    }
    

    然后创建转场片段GradientTransferSegment,其父类TransitionSegment同时持有上一个与下一个片段,
    可以在其基础上实现任意转场功能

    public class GradientTransferSegment extends TransitionSegment<FitCenterScaleSegment, FitCenterScaleSegment> {
        /**
         * 缩放动画范围
         */
        private float mPreScaleFrom;
        private float mPreScaleTo;
        private float mNextScaleFrom;
        private float mNextScaleTo;
    
        public GradientTransferSegment(int duration,
                                       float preScaleFrom, float preScaleTo,
                                       float nextScaleFrom, float nextScaleTo) {
            mPreScaleFrom = preScaleFrom;
            mPreScaleTo = preScaleTo;
            mNextScaleFrom = nextScaleFrom;
            mNextScaleTo = nextScaleTo;
            setDuration(duration);
        }
    
        @Override
        protected void onDataPrepared() {
    
        }
    
        @Override
        public void drawFrame(GLESCanvas canvas, float segmentProgress) {
            //下一个片段开始放大
            float nextScale = mNextScaleFrom + (mNextScaleTo - mNextScaleFrom) * segmentProgress;
            mNextSegment.drawContent(canvas, nextScale);
    
            //上一个片段继续放大同时变透明
            float preScale = mPreScaleFrom + (mPreScaleTo - mPreScaleFrom) * segmentProgress;
            float alpha = 1 - segmentProgress;
            mPreSegment.drawBackground(canvas);
            canvas.save();
            canvas.setAlpha(alpha);
            mPreSegment.drawContent(canvas, preScale);
            canvas.restore();
        }
    

    创建照片电影

        private static PhotoMovie initGradientPhotoMovie(PhotoSource photoSource) {
            List<MovieSegment> segmentList = new ArrayList<>(photoSource.size());
            for (int i = 0; i < photoSource.size(); i++) {
                if (i == 0) {
                    segmentList.add(new FitCenterScaleSegment(1600, 1f, 1.1f));
                } else {
                    segmentList.add(new FitCenterScaleSegment(1600, 1.05f, 1.1f));
                }
                if (i < photoSource.size() - 1) {
                    segmentList.add(new GradientTransferSegment(800, 1.1f, 1.15f, 1.0f, 1.05f));
                }
            }
            return new PhotoMovie(photoSource, segmentList);
        }
    

    然后将这个PhotoMovie正常播放即可,效果如下

    gradient.gif

    扩展滤镜

    目前内置了9个滤镜

    public enum  FilterType {
        NONE,
        CAMEO,//浮雕
        GRAY,//黑白
        KUWAHARA,//水彩
        SNOW,//飘雪(动态)
        LUT1,
        LUT2,
        LUT3,
        LUT4,
        LUT5,
    }
    

    先看IMovieFilter

    public interface IMovieFilter {
        void doFilter(PhotoMovie photoMovie,int elapsedTime, FboTexture inputTexture, FboTexture outputTexture);
        void release();
    }
    

    外部会提供一个输入纹理,然后由IMovieFilter处理之后绘制到输出纹理上,即实现了滤镜效果

    BaseMovieFilter已经实现了基本的输入输出流程,例如要做最基本的黑白滤镜,只需更换FRAGMENT_SHADER即可

    public class GrayMovieFilter extends BaseMovieFilter {
        protected static final String FRAGMENT_SHADER = "" +
                "varying highp vec2 textureCoordinate;\n" +
                " \n" +
                "uniform sampler2D inputImageTexture;\n" +
                " \n" +
                "void main()\n" +
                "{\n" +
                "     mediump vec4 color = texture2D(inputImageTexture, textureCoordinate);\n" +
                "     mediump float gray = color.r*0.3+color.g*0.59+color.b*0.11;\n"+
                "     gl_FragColor = vec4(gray,gray,gray,1.0);\n"+
                "}";
        public GrayMovieFilter(){
            super(VERTEX_SHADER,FRAGMENT_SHADER);
        }
    }
    

    同时PhotoMovie提供了对Lut滤镜的支持

    Lut其实就是Lookup Table(颜色查找表),根据原图的RGB值去相应的lut图里面查找对应转换后的RGB值,从而实现各种滤镜效果

    lut 效果
    lut(原图) 原图
    lut_2.jpg 滤镜效果图
    public class LutMovieFilter extends TwoTextureMovieFilter {
    
        public LutMovieFilter(Bitmap lutBitmap){
            super(loadShaderFromAssets("shader/two_vertex.glsl"),loadShaderFromAssets("shader/lut.glsl"));
            setBitmap(lutBitmap);
        }
    }
    

    在LutMovieFilter的构造函数传入上面表格里的lut图,即可实现相应的滤镜效果,前面提到的黑白滤镜也可用这个方式实现

    录制功能

    GLMovieRecorder提供了将照片电影录制为mp4的功能

    可参照DemoPresenter的saveVideo()函数

            GLMovieRecorder recorder = new GLMovieRecorder();
            recorder.configOutput(width, height(), bitrate,frameRate,iFrameInterval, outputPath);
            recorder.setDataSource(movieRenderer);
            recorder.startRecord(new GLMovieRecorder.OnRecordListener() {
                @Override
                public void onRecordFinish(boolean success) {
                   ......
                }
    
                @Override
                public void onRecordProgress(int recordedDuration, int totalDuration) {
                   ......
                }
            });
    

    背景音乐

     mPhotoMoviePlayer.setMusic(context, mMusicUri);
    

    PhotoMovie只提供了播放背景音乐的功能,录制完成之后需自行合成,Demo里使用了
    VideoProcessor进行合成

     VideoProcessor.mixAudioTrack(context, videPath, audioPath,outputPath, null, null, 0,100, 1f, 1f);
    

    相关文章

      网友评论

      本文标题:Android高仿抖音照片电影功能

      本文链接:https://www.haomeiwen.com/subject/itlbgftx.html