继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Android高仿抖音照片电影功能

幕布斯6054654
关注TA
已关注
手记 1281
粉丝 219
获赞 1011

滤镜效果

https://img3.mukewang.com/5d2c7b810001458103700606.jpg

filter.gif

转场效果

https://img.mukewang.com/5d2c7b93000191f403830605.jpg

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使用模块化的设计,每个部分都可以自定义然后替换,主要类图如下


https://img4.mukewang.com/5d2c7bad0001a12206940634.jpg

PhotoMovie.png


,在这段时间之内以特定的方式播放图片,例如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//纵向平移
    }

这里以微视的渐变特效为例展示如何扩展
分析得出,渐变特效首先图片居中放置,然后全程做一个微弱的放大动画,后半部分同时透明度变化消失
,更直观的流程如下图

https://img4.mukewang.com/5d2c7bc000017ed307140160.jpg

gradient_timeline.png


可见需要两个不同的片段类型
首先创建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正常播放即可,效果如下

https://img2.mukewang.com/5d2c7bed0001119403860601.jpg

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效果

https://img3.mukewang.com/5d2c7c180001ede502730278.jpg

lut(原图)

https://img1.mukewang.com/5d2c7c1c0001e53503280212.jpg

原图

https://img3.mukewang.com/5d2c7c300001154a02800280.jpg

lut_2.jpg

https://img1.mukewang.com/5d2c7c34000118e503240214.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,



作者:yellowcath
链接:https://www.jianshu.com/p/863b65e4a9e4


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP