自定义Camera实现头像框效果,并裁剪指定区域合成
需要一个带框的相机,并且拍好后能合成框和人脸,不过需要人自己凑过去哈哈哈
这两天看了很多博客,然后自己根据自己的要求改了改,基本可以用,调节参数可以获得想要的效果
参考链接在最后面,要是看不懂我的,可以去看看他们的,都挺有参考价值的
效果图:
前一张为拍照界面,绿色的是拍照按钮。。。第二章为保存的照片,叠加了背景,中间是裁剪出来的,最上面一层是图片,一共三层。
实现思路:
自定义相机
在surface界面上加一个自定义的view,画上(或者直接覆盖)这张图片,
拍好后,在照片上裁剪出小恐龙图片大小的区域
选好背景,调好裁剪后的照片和小恐龙的位置,叠加照片
注意点:
裁剪时,比例是按照预览窗口中小恐龙控件的比例去裁剪
由于是自定义的相机,所以预览窗口(Preview)大小和照片(Picture)的大小都需要自己设置过。我这里是给了他两个相同的固定值,使其表现的一样。
需要适配的话,可以修改代码中的changePreviewSize方法,在其中设置预览窗口和照片大小,注意,一定要设置成宽高比例差不多的,不然保存的照片会出现拉伸的问题。
相机的自动聚焦,对于不同的机型可能会出现问题,问题的解决参考链接在最后
对于6.0以后,权限的申请我用的是第三方,EasyPermissions,使用方法见参考链接
不BB了,代码
1.在gradle里添加依赖(EasyPermissions)
implementation 'pub.devrel:easypermissions:1.3.0'
2.加权限
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
3.activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<FrameLayout
android:id="@+id/myFramelayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/combine_pic3" />
<SurfaceView
android:id="@+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.example.zhang.diycamerademo01.MyView
android:id="@+id/myView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</FrameLayout>
<LinearLayout
android:id="@+id/linear"
android:layout_width="55dp"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_marginRight="8dp"
android:gravity="center_vertical">
<Button
android:id="@+id/takepicture_button"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@drawable/ic_launcher_background"
android:onClick="takepicture" />
</LinearLayout>
</RelativeLayout>
4.MyView.java
public class MyView extends View {
//获取屏幕的宽和高。根据屏幕的宽和高来算框的位置
private int screenWidth, screenHeight;
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setMyParams(int screenWidth, int screenHeight) {
this.screenWidth = screenWidth;
this.screenHeight = screenHeight;
}
@Override
public void onDraw(Canvas canvas) {
Bitmap bg1 = ((BitmapDrawable) getResources().getDrawable(R.drawable.combine_touxiang3)).getBitmap();
//Bitmap toghter = ((BitmapDrawable) getResources().getDrawable(R.drawable.combine_toghter1small)).getBitmap();
//画一个头像框,正中间
canvas.drawBitmap(bg1,(screenWidth-bg1.getWidth())/2,(screenHeight-bg1.getHeight())/2,null);
//canvas.drawBitmap(toghter,0,0,null);
super.onDraw(canvas);
}
}
public class MainActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks {
private static final String TAG = "TEST";
private MyView myView;
private Camera camera;
private SurfaceView surfaceview;
private int screenWidth, screenHeight;
private ToneGenerator tone;
private Camera.Parameters parameters;
//剪切后的图片宽高
private int retX, retY;
private float radioX,radioY;
String[] permissions = new String[]{
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
};
// 声明一个集合,在后面的代码中用来存储用户拒绝授权的权
List<String> mPermissionList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
//把屏幕设置成横屏
setContentView(R.layout.activity_main);
//全屏
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
initRequest();
WindowManager wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);
screenWidth = wm.getDefaultDisplay().getWidth();
screenHeight = wm.getDefaultDisplay().getHeight();
initScale();
myView = findViewById(R.id.myView);
myView.setMyParams(screenWidth, screenHeight);
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) myView.getLayoutParams();
layoutParams.width = screenWidth;
layoutParams.height = screenHeight;
myView.setLayoutParams(layoutParams);
initDIYCamera();
}
private Camera.AutoFocusCallback myAutoFocusCallback = new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
Log.w("print", "聚焦完成,,,,");
//聚焦后发出提示音
tone = new ToneGenerator(AudioManager.STREAM_MUSIC, ToneGenerator.MAX_VOLUME);
tone.startTone(ToneGenerator.TONE_PROP_BEEP2);
}
};
private void initScale(){
Bitmap bg3 = ((BitmapDrawable) getResources().getDrawable(R.drawable.combine_touxiang3)).getBitmap();
int test1=bg3.getHeight();
int test2=bg3.getWidth();
radioX=(float) test2/screenWidth;
radioY=(float) test1/screenHeight;
}
private void initDIYCamera() {
//当第一次进来时,首先执行这个方法,因为没有授权所以找不到相机
//所以需要再授权后再次找到相机
myView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
camera.autoFocus(myAutoFocusCallback);
return false;
}
});
camera = Camera.open(0);
surfaceview = (SurfaceView) findViewById(R.id.surfaceView);
SurfaceHolder holder = surfaceview.getHolder();
holder.addCallback(new MySurfaceCallback());
holder.setKeepScreenOn(true);// 屏幕常亮 holder.setType(SurfaceHolder.SURFACE_TYPE_NORMAL);
holder.lockCanvas();
}
private void initRequest() {
//使用easyPermissions
//先检查是否已经授权
if (EasyPermissions.hasPermissions(this, permissions)) {
// 已经申请过权限,做想做的事
} else {
// 没有申请过权限,现在去申请,第二个参数为拒绝后再次申请时弹出的文字
EasyPermissions.requestPermissions(this, "快来申请我哦",
1, permissions);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
// 把执行结果的操作给EasyPermissions
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
}
//申请成功时调用
@Override
public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {
//请求成功执行相应的操作
//比如,举个例子
switch (requestCode) {
case 1:
//第一次进来,初始化时camera为空
camera.open(0);
Toast.makeText(this, "成功获取3个权限", Toast.LENGTH_SHORT).show();
break;
}
}
@Override
public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {
//比如,举个例子
switch (requestCode) {
case 1:
Toast.makeText(this, "3个权限未被全部同意", Toast.LENGTH_SHORT).show();
break;
}
}
private final class MySurfaceCallback implements SurfaceHolder.Callback {
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
//当surface的格式或大小发生改变,这个方法就被调用
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
try {
if (camera==null){
camera = Camera.open(0);
}
camera.stopPreview();
camera.release();
camera = null;
camera = Camera.open(0);
//设置相机预览时大小及图片的大小和camera的参数设置
changePreviewSize(camera, surfaceview.getWidth(), surfaceview.getHeight());
camera.setPreviewDisplay(surfaceview.getHolder());
// 开启预览
camera.startPreview();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (camera != null) {
camera.release();
camera = null;
}
}
}
public void takepicture(View v) {
camera.takePicture(mShutterCallback, null, mPictureCallback);
}
Camera.ShutterCallback mShutterCallback = new Camera.ShutterCallback() {
public void onShutter() {
}
};
private Camera.PictureCallback mPictureCallback = new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
Bitmap mBitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
Bitmap mBitmapCut = ImageCrop(mBitmap);
Bitmap bg1 = ((BitmapDrawable) getResources().getDrawable(R.drawable.combine_pic1)).getBitmap();
Bitmap bg3 = ((BitmapDrawable) getResources().getDrawable(R.drawable.combine_touxiang3)).getBitmap();
Bitmap mBitmapFinal = CombineImage(bg1, mBitmapCut, bg3);
String rootPath = Environment.getExternalStorageDirectory().toString() + File.separator;
File file = new File(rootPath + System.currentTimeMillis() + ".jpg");
try {
file.createNewFile();
BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(file));
mBitmapFinal.compress(Bitmap.CompressFormat.JPEG, 100, os);//100 是压缩率,100表示不压缩 os.flush();
os.close();
Toast.makeText(getApplicationContext(), "保存成功", Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
}
}
};
/**
* 修改相机的预览尺寸和保存的相片的尺寸以及camera的属性
*
* @param camera 相机实例
* @param viewWidth 预览的surfaceView的宽
* @param viewHeight 预览的surfaceView的高
*/
protected void changePreviewSize(Camera camera, int viewWidth, int viewHeight) {
if (camera == null) {
return;
}
parameters = camera.getParameters();
List<Camera.Size> sizeList = parameters.getSupportedPreviewSizes();
Camera.Size closelySize = null;//储存最合适的尺寸
for (Camera.Size size : sizeList) { //先查找preview中是否存在与surfaceview相同宽高的尺寸
Log.i(TAG, "preview size.width=" + size.width + " size.height=" + size.height);
if ((size.width == viewWidth) && (size.height == viewHeight)) {
closelySize = size;
}
}
if (closelySize == null) {
// 得到与传入的宽高比最接近的size
float reqRatio = ((float) viewWidth) / viewHeight;
float curRatio, deltaRatio;
float deltaRatioMin = Float.MAX_VALUE;
for (Camera.Size size : sizeList) {
if (size.width < 1024) continue;//1024表示可接受的最小尺寸,否则图像会很模糊,可以随意修改
curRatio = ((float) size.width) / size.height;
deltaRatio = Math.abs(reqRatio - curRatio);
if (deltaRatio < deltaRatioMin) {
deltaRatioMin = deltaRatio;
closelySize = size;
}
}
}
//设置裁剪后全屏高清的代码
// 获取摄像头支持的PictureSize列表
List<Camera.Size> pictureSizeList = parameters.getSupportedPictureSizes();
List<Camera.Size> middlePicSizeList = new ArrayList<>();//储存比例最接近且与预览窗口最接近大小的尺寸
Camera.Size closelyPicSize = null;//储存最合适的照片尺寸
for (Camera.Size size : pictureSizeList) { //先查找Pic中是否存在与surfaceview相同宽高的尺寸
Log.i(TAG, "pictureSizeList size.width=" + size.width + " size.height=" + size.height);
if ((size.width == viewWidth) && (size.height == viewHeight)) {
closelyPicSize = size;
}
}
if (closelyPicSize == null) {
// 得到与传入的宽高比最接近的PicSize
float reqRatio = ((float) viewWidth) / viewHeight;
float curRatio, deltaRatio;
float deltaRatioMin = Float.MAX_VALUE;
for (Camera.Size size : pictureSizeList) {
if (size.width < 1024) continue;//1024表示可接受的最小尺寸,否则图像会很模糊,可以随意修改
curRatio = ((float) size.width) / size.height;
deltaRatio = Math.abs(reqRatio - curRatio);
if (deltaRatio < deltaRatioMin) {
deltaRatioMin = deltaRatio;
middlePicSizeList.add(size);
}
}
for (Camera.Size size:middlePicSizeList){
float middleSize;
Log.i(TAG, "pictureMiddleList size.width=" + size.width + " size.height=" + size.height);
float middleSi* = Float.MAX_VALUE;
middleSize = Math.abs(size.width - closelySize.width);
if (middleSize<middleSi*){
middleSi* = middleSize;
closelyPicSize=size;
}
}
}
if (closelySize != null) {
Log.i("changePreviewSize", "预览尺寸修改为:" + closelySize.width + "*" + closelySize.height);
//设置大小
//parameters.setPreviewSize(closelySize.width, closelySize.height);
//由于裁剪是依据preview界面中MyView的比例来算的,所以preview界面和保存的照片的
// 宽高比例要一致,才不会导致拉伸
parameters.setPreviewSize(1920, 1080);
//设置图片的大小,同样可以和preview一样修改,只是现在把他弄成了固定数值
parameters.setPictureSize(1920, 1080);
//设置自动对焦
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
camera.setParameters(parameters);
}
}
/**
* 按图片比例裁切图片
*/
public Bitmap ImageCrop(Bitmap bitmap) {
// 得到图片的宽,高
int w = bitmap.getWidth();
int h = bitmap.getHeight();
//剪切的初始xy位置,原点位置都是根据preview
//界面的控件比例来算的
int x = (int) ((w-(w * radioX ))/2);
int y = (int) ((h-(h * radioY )) /2);
//剪切后的宽和高
retX = (int) (w * radioX);
retY = (int) (h * radioY);
Bitmap afterCrop = Bitmap.createBitmap(bitmap, x, y, retX, retY, null, false);
return afterCrop;
}
/*
叠加图片
*/
public Bitmap CombineImage(Bitmap b1, Bitmap b2, Bitmap b3) {
//创建一张大小和背景图一致的位图
int bgWidth = b1.getWidth();
int bgHeight = b1.getHeight();
int fgWidth = b2.getWidth(); //前景图宽度,用于后面计算前景图的绘制坐标
int fgHeight = b2.getHeight(); //前景图高度,用于后面计算前景图的绘制坐标
Bitmap newbmp = Bitmap.createBitmap(bgWidth, bgHeight, Bitmap.Config.RGB_565);
//缩放照片
int beforeX=b3.getWidth();
int beforeY=b3.getHeight();
float scaleWidth = ((float) retX) / beforeX;
float scaleHeight = ((float) retY) / beforeY;
// 取得想要缩放的matrix參數
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);//两个参数未缩放的比例
Bitmap afterCrop = Bitmap.createBitmap(b3, 0, 0, beforeX, beforeY, matrix, true);
//新建画布
Canvas cv = new Canvas(newbmp);
//在0,0坐标开始画入bg
cv.drawBitmap(b1, 0, 0, null);
//开始画入fg,可以从任意位置画入,具体位置自己计算f
cv.drawBitmap(b2, (bgWidth - fgWidth) / 2, (bgHeight - fgHeight) / 2, null);
//开始画入第三层,可以从任意位置画入,具体位置自己计算f
cv.drawBitmap(afterCrop, (bgWidth - fgWidth) / 2, (bgHeight - fgHeight) / 2, null);
return newbmp;
}
}
总结:
这个demo还是很不完整的,很多的细节,比如位置等,都是需要自己看着调的,自己通过改一些东西也能实现大部分功能。
加油!!!哈哈哈
demo下载:https://download.csdn.net/download/jiye111/10626903
参考链接:
自定义相机: https://blog.csdn.net/ming_csdn_/article/details/70154381
自动对焦:https://blog.csdn.net/yanzi1225627/article/details/8577682
图片叠加功能:https://www.jb51.net/article/138971.htm
EasyPermissions使用方法:https://blog.csdn.net/u011138190/article/details/79776902
Fail to connect to camera service的几种原因和解决方法:https://blog.csdn.net/wang_shuai_ww/article/details/44560113
上一篇: html2canvas 如何生成高清图片