滤镜效果
filter.gif
转场效果
transfer.gif
基本用法//添加图片
List photoDataList = new LinkedList();
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使用模块化的设计,每个部分都可以自定义然后替换,主要类图如下
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//纵向平移
}
这里以微视的渐变特效为例展示如何扩展
分析得出,渐变特效首先图片居中放置,然后全程做一个微弱的放大动画,后半部分同时透明度变化消失
,更直观的流程如下图
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 { /**
* 缩放动画范围
*/
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 segmentList = new ArrayList<>(photoSource.size()); for (int i = 0; i
segmentList.add(new FitCenterScaleSegment(1600, 1f, 1.1f));
} else {
segmentList.add(new FitCenterScaleSegment(1600, 1.05f, 1.1f));
} if (i
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,
}
先看IMovieFilterpublic 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,
作者:yellowcath
链接:https://www.jianshu.com/p/863b65e4a9e4