Skip to content

Instantly share code, notes, and snippets.

@esabook
Last active May 19, 2022 07:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save esabook/a712d8955483f5b0f685eb3ab15abbc1 to your computer and use it in GitHub Desktop.
Save esabook/a712d8955483f5b0f685eb3ab15abbc1 to your computer and use it in GitHub Desktop.
[Android] CameraActivity: FrontCam/MainCam, GreenScreen base for MaskingIndicator/Crop
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000">
<include
android:id="@+id/icl_toolbar"
layout="@layout/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true" />
<com.esabook.app.widget.AspectFrameLayout
android:id="@+id/vg_camera"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/icl_toolbar"
android:layout_centerInParent="true">
<com.esabook.app.widget.CameraSurfaceView
android:id="@+id/camera"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"
android:longClickable="false">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/overlay_outer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/overlay"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="@dimen/padding_24"
android:adjustViewBounds="true"
android:visibility="invisible"
app:srcCompat="@drawable/ic_camera_mask_rectangle"
tools:visibility="visible" />
</FrameL
ayout>
</com.esabook.app.widget.AspectFrameLayout>
<LinearLayout
android:id="@+id/vg_footer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="@color/black_60"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_weight="0.7"
android:gravity="right"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/bt_capture"
android:layout_width="wrap_content"
android:layout_height="70dp"
android:layout_gravity="center"
android:layout_marginTop="12dp"
android:layout_marginBottom="@dimen/size_12"
android:adjustViewBounds="true"
android:background="?selectableItemBackgroundBorderless"
app:srcCompat="@drawable/ic_camera_capture" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.5"
android:gravity="right"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/button_switch_camera"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/padding_16"
android:layout_marginRight="@dimen/padding_16"
android:layout_marginEnd="@dimen/padding_16"
android:layout_gravity="right|center"
android:adjustViewBounds="true"
android:background="?selectableItemBackgroundBorderless"
app:srcCompat="@drawable/ic_switch_camera_white_24dp" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
</layout>
package *;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import timber.log.Timber;
/**
* Layout that adjusts to maintain a specific aspect ratio.
*/
public class AspectFrameLayout extends FrameLayout {
private double mTargetAspect = -1.0; // initially use default window size
public AspectFrameLayout(Context context) {
super(context);
}
public AspectFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* Sets the desired aspect ratio. The value is <code>width / height</code>.
*/
public void setAspectRatio(double aspectRatio) {
if (aspectRatio < 0) {
throw new IllegalArgumentException();
}
Timber.d("Setting aspect ratio to %s (was %s)", aspectRatio, mTargetAspect);
if (mTargetAspect != aspectRatio) {
mTargetAspect = aspectRatio;
requestLayout();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Timber.d("onMeasure target=%s width=[%s] height=[%s]",
mTargetAspect,
MeasureSpec.toString(widthMeasureSpec),
MeasureSpec.toString(heightMeasureSpec));
// Target aspect ratio will be < 0 if it hasn't been set yet. In that case,
// we just use whatever we've been handed.
if (mTargetAspect > 0) {
int initialWidth = MeasureSpec.getSize(widthMeasureSpec);
int initialHeight = MeasureSpec.getSize(heightMeasureSpec);
// factor the padding out
int horizPadding = getPaddingLeft() + getPaddingRight();
int vertPadding = getPaddingTop() + getPaddingBottom();
initialWidth -= horizPadding;
initialHeight -= vertPadding;
double viewAspectRatio = (double) initialWidth / initialHeight;
double aspectDiff = mTargetAspect / viewAspectRatio - 1;
if (Math.abs(aspectDiff) < 0.01) {
// We're very close already. We don't want to risk switching from e.g. non-scaled
// 1280x720 to scaled 1280x719 because of some floating-point round-off error,
// so if we're really close just leave it alone.
Timber.d("aspect ratio is good (target=%s, view=%dx%d)",
mTargetAspect, initialWidth, initialHeight);
} else {
if (aspectDiff > 0) {
// limited by narrow width; restrict height
initialHeight = (int) (initialWidth / mTargetAspect);
} else {
// limited by short height; restrict width
initialWidth = (int) (initialHeight * mTargetAspect);
}
Timber.d("new size=%dx%d + padding %dx%d",
initialWidth, initialHeight, horizPadding, vertPadding);
initialWidth += horizPadding;
initialHeight += vertPadding;
widthMeasureSpec = MeasureSpec.makeMeasureSpec(initialWidth, MeasureSpec.EXACTLY);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(initialHeight, MeasureSpec.EXACTLY);
}
}
Timber.d("set width=[%s] height=[%s]",
MeasureSpec.toString(widthMeasureSpec),
MeasureSpec.toString(heightMeasureSpec));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
package *;
/*
* Copyright 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.content.Context;
import android.util.AttributeSet;
import android.view.TextureView;
/**
* A {@link TextureView} that can be adjusted to a specified aspect ratio.
*/
public class AutoFitTextureView extends TextureView {
private int mRatioWidth = 0;
private int mRatioHeight = 0;
public AutoFitTextureView(Context context) {
this(context, null);
}
public AutoFitTextureView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
* calculated from the parameters. Note that the actual sizes of parameters don't matter, that
* is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
*
* @param width Relative horizontal size
* @param height Relative vertical size
*/
public void setAspectRatio(int width, int height) {
if (width < 0 || height < 0) {
throw new IllegalArgumentException("Size cannot be negative.");
}
mRatioWidth = width;
mRatioHeight = height;
requestLayout();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (0 == mRatioWidth || 0 == mRatioHeight) {
setMeasuredDimension(width, height);
} else {
if (width < height * mRatioWidth / mRatioHeight) {
setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
} else {
setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
}
}
}
}
package *;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.hardware.Camera;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.util.DisplayMetrics;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import *.BuildConfig;
import *.R;
import *.databinding.ActivityCameraBinding;
import *.dialog.ProgressDialog2;
import *.router.CameraActivityEvent;
import *.util.DeleteFileOnExit;
import *.util.jdk8.Optional;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.SimpleTarget;
import com.bumptech.glide.request.transition.Transition;
import org.greenrobot.eventbus.EventBus;
import java.io.File;
import java.io.FileOutputStream;
import java.io.Serializable;
import timber.log.Timber;
/**
* example usage
* <p>
* startActivity(new Intent(this, CameraActivity.class));
* <p>
* <p>
* listen result:
*
* @Subscribe public void onEvent(Uri event){
* Glide.with(this)
* .load(event)
* .listener(...)
* .into(...);
* }
* <p>
* OR
* @Override public void onActivityResult(int requestCode, int resultCode, Intent data) {
* super.onActivityResult(requestCode, resultCode, data);
* Uri uri = data.getData();
* }
*/
public class CameraActivity extends AppCompatActivity {
public final static String RESULT_IMAGE_URI = "result_image_uri";
public final static String ARG_PAYLOAD_DATA = "arg_payload_data";
ProgressDialog2 pdialog = null;
MaskingAsyncTask maskingGenerator;
private ActivityCameraBinding binding;
private PayloadData payloadData;
public static Intent newInstance(Context context, PayloadData payloadData) {
Intent i = new Intent(context, CameraActivity.class);
i.putExtra(ARG_PAYLOAD_DATA, payloadData);
return i;
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityCameraBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
pdialog = new ProgressDialog2(CameraActivity.this);
try {
payloadData = (PayloadData) getIntent().getExtras().getSerializable(ARG_PAYLOAD_DATA);
} catch (Exception ignore) {
payloadData = new PayloadData();
}
if (payloadData.cameraFacingFrontMode) {
binding.camera.setCameraFacing(Camera.CameraInfo.CAMERA_FACING_FRONT);
} else {
binding.camera.setCameraFacing(Camera.CameraInfo.CAMERA_FACING_BACK);
}
Drawable overlay = getResources().getDrawable(payloadData.overlayDrawable);
if (overlay != null) {
binding.overlay.setImageDrawable(overlay);
binding.overlay.getViewTreeObserver().addOnGlobalLayoutListener(this::setupMask);
} else {
payloadData.cropModeEnabled = false;
}
if (!isCameraPermissionGranted() || !isStoragePermissionGranted()) {
binding.vgCamera.setVisibility(View.GONE);
requestAccessPermission();
}
binding.btCapture.setOnClickListener(this::capture);
binding.buttonSwitchCamera.setOnClickListener(this::switching);
initToolbar();
}
private void initToolbar() {
if (binding == null) return;
if (payloadData == null) return;
if (!payloadData.showToolbar) {
binding.iclToolbar.toolbar.setVisibility(View.GONE);
return;
}
binding.iclToolbar.toolbarTitle.setText(payloadData.toolbarTitle);
binding.iclToolbar.toolbar.setNavigationIcon(R.drawable.ic_close_black_24dp);
setSupportActionBar(binding.iclToolbar.toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override
public boolean onSupportNavigateUp() {
onBackPressed();
return super.onSupportNavigateUp();
}
@Override
public void onBackPressed() {
super.onBackPressed();
EventBus.getDefault().post(new CameraActivityEvent.CancelClose());
}
@Override
protected void onResume() {
super.onResume();
if (isCameraPermissionGranted() && isStoragePermissionGranted()) {
binding.vgCamera.setVisibility(View.VISIBLE);
binding.camera.stopPreview();
binding.camera.startPreview();
}
}
boolean isStoragePermissionGranted() {
return PackageManager.PERMISSION_GRANTED ==
ContextCompat.checkSelfPermission(this,
Manifest.permission.READ_EXTERNAL_STORAGE);
}
boolean isCameraPermissionGranted() {
return PackageManager.PERMISSION_GRANTED ==
ContextCompat.checkSelfPermission(this,
Manifest.permission.CAMERA);
}
void requestAccessPermission() {
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.READ_EXTERNAL_STORAGE) || !isStoragePermissionGranted()) {
toast("Izin eksternal memori diperlukan untuk aplikasi ini.");
}
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.CAMERA) || !isCameraPermissionGranted()) {
toast("Izin akses camera diperlukan untuk aplikasi ini.");
}
ActivityCompat.requestPermissions(this, new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.CAMERA
}, 0);
}
void setupMask() {
if (maskingGenerator == null) {
maskingGenerator = new MaskingAsyncTask();
maskingGenerator.execute(binding);
} else {
maskingGenerator.cancel(true);
maskingGenerator = null;
setupMask();
}
}
Bitmap convertToBitmap(Drawable drawable, int W, int H) {
Bitmap mutableBitmap = Bitmap.createBitmap(W, H, Bitmap.Config.ARGB_8888);
Canvas newCanvas = new Canvas(mutableBitmap);
drawable.setBounds(0, 0, W, H);
drawable.draw(newCanvas);
return mutableBitmap;
}
void capture(View v) {
Optional.ofNullable(binding.camera.getCamera()).ifPresent(it ->
it.takePicture(null, null, (data, camera) -> {
File pictureFile = createPhotoFile();
if (pictureFile == null) {
Timber.d("Error creating media file, check storage permissions");
return;
}
try {
runOnUiThread(new Runnable() {
@Override
public void run() {
pdialog.setMessage("Memproses gambar ... ");
pdialog.show();
}
});
FileOutputStream fos = new FileOutputStream(pictureFile);
fos.write(data);
fos.close();
if (payloadData.cropModeEnabled) {
dispatchCallbackWithCrop(pictureFile);
} else {
dispatchCallbackNoCrop(pictureFile);
}
} catch (Exception e) {
Timber.w(e);
}
}));
}
void switching(View v) {
if (v != null) {
payloadData.cameraFacingFrontMode = !payloadData.cameraFacingFrontMode;
binding.camera.stopPreview();
binding.camera.switchCameraMode();
binding.camera.startPreview();
}
}
private File createPhotoFile() {
try {
String filePhotoName = System.currentTimeMillis() + ".jpg";
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
return new File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
filePhotoName);
} else {
return File.createTempFile(
filePhotoName,
null,
getExternalFilesDir(Environment.DIRECTORY_PICTURES));
}
} catch (Exception e) {
Timber.w(e);
return null;
}
}
private Uri getPhotoFileUri(File file) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
return Uri.fromFile(file);
} else {
return FileProvider.getUriForFile(this, BuildConfig.FILE_PROVIDER, file);
}
}
void dispatchCallbackWithCrop(File imageFile) {
Glide.with(this)
.asBitmap()
.load(imageFile)
.dontTransform()
.into(new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap bitmap,
@Nullable Transition<? super Bitmap> transition) {
// crop size
float markerWPercent = (float) binding.overlay.getWidth() * 100 / binding.camera.getWidth();
float croppedWidth = bitmap.getWidth() * markerWPercent / 100f;
float markerHPercent = (float) binding.overlay.getHeight() * 100 / binding.camera.getHeight();
float croppedHeight = bitmap.getHeight() * markerHPercent / 100f;
// crop at center
float x = (bitmap.getWidth() - croppedWidth) / 2;
float y = (bitmap.getHeight() - croppedHeight) / 2;
Bitmap cropBmp = Bitmap.createBitmap(bitmap,
(int) x, (int) y,
(int) croppedWidth, (int) croppedHeight);
Timber.d("CROP: \nOriW=%s\nOriH=%s\nCropW/p =%s\nCropH/p =%s\nCropW=%s\nCropH=%s\nX=%s\nY=%s\nResultWH=%s x %s",
bitmap.getWidth(), bitmap.getHeight(),
markerWPercent, markerHPercent,
croppedWidth, croppedHeight,
x, y, cropBmp.getWidth(), cropBmp.getHeight());
saveBitmapToFile(cropBmp, imageFile);
dispatchCallback(imageFile);
}
});
}
void dispatchCallbackNoCrop(File file) {
Glide.with(this)
.asBitmap()
.load(file)
.dontTransform()
.into(new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap bitmap,
@Nullable Transition<? super Bitmap> transition) {
saveBitmapToFile(bitmap, file);
dispatchCallback(file);
}
});
}
void saveBitmapToFile(Bitmap bitmap, File file) {
try (FileOutputStream out = new FileOutputStream(file)) {
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
} catch (Exception e) {
Timber.d(e);
}
}
void dispatchCallback(File file) {
DeleteFileOnExit.add(file);
Uri resultUri = getPhotoFileUri(file);
// dispatch with eventbus
EventBus.getDefault().post(resultUri);
// dispatch with onresult
Intent result = new Intent(RESULT_IMAGE_URI, resultUri);
setResult(Activity.RESULT_OK, result);
finish();
}
void toast(String msg) {
Toast.makeText(this,
msg,
Toast.LENGTH_LONG)
.show();
}
private int getWidthandHeightScreen(int flagReturn) {
DisplayMetrics displaymetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
int height = displaymetrics.heightPixels;
int width = displaymetrics.widthPixels;
if (flagReturn == 0) {
return height;
} else {
return width;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (pdialog != null) {
if (pdialog.isShowing()) {
pdialog.dismiss();
}
}
}
// Image Rotator
// https://gist.github.com/tomogoma/788e3b775dd611c9226f8e17781a0f0c
public static class PayloadData implements Serializable {
public String toolbarTitle;
public boolean showToolbar = true;
public boolean cropModeEnabled = true;
public boolean cameraFacingFrontMode = false;
public int outerBackgroundColor = 0x5c000000;
public @DrawableRes
int overlayDrawable = R.drawable.ic_camera_mask_rectangle;
}
class MaskingAsyncTask extends AsyncTask<ActivityCameraBinding, Void, BitmapDrawable> {
@Override
protected BitmapDrawable doInBackground(ActivityCameraBinding... bindings) {
try {
ActivityCameraBinding b = bindings[0];
int maxDispayW = b.camera.getWidth();
int maxDispayH = b.camera.getHeight();
if (maxDispayH <= 0 || maxDispayW <= 0) return null;
// draw background
Bitmap outerBg = Bitmap.createBitmap(maxDispayW, maxDispayH, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(outerBg);
Paint bg = new Paint();
bg.setStyle(Paint.Style.FILL);
bg.setColor(payloadData.outerBackgroundColor);
c.drawPaint(bg);
// draw masking hole, crop
Drawable maskDrawable = b.overlay.getDrawable();
Bitmap mask = convertToBitmap(maskDrawable,
b.overlay.getWidth(), b.overlay.getHeight());
float maskX = b.overlay.getX();
float maskY = b.overlay.getY();
Paint rectPaint = new Paint();
rectPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
rectPaint.setStyle(Paint.Style.FILL);
rectPaint.setAntiAlias(true);
c.drawBitmap(mask, maskX, maskY, rectPaint);
mask.recycle();
// draw original color, exclude Green color
// The matrix is stored in a single array, and its treated as follows: [ a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t ]
// When applied to a color [r, g, b, a], the resulting color is computed as (after clamping) ;
// R' = a*R + b*G + c*B + d*A + e;
// G' = f*R + g*G + h*B + i*A + j;
// B' = k*R + l*G + m*B + n*A + o;
// A' = p*R + q*G + r*B + s*A + t;
float[] matrix = {
1, 0, 0, 0, 0, //red
0, 1, 0, 0, 0, //green
0, 0, 1, 0, 0, //blue
0, -1, 0, 1, 0, //alpha
};
ColorMatrixColorFilter removeGreenFilter = new ColorMatrixColorFilter(matrix);
Drawable maskDEx = maskDrawable.getConstantState().newDrawable();
maskDEx.setColorFilter(removeGreenFilter);
Bitmap maskEx = convertToBitmap(maskDEx,
b.overlay.getWidth(), b.overlay.getHeight());
rectPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
c.drawBitmap(maskEx, maskX, maskY, rectPaint);
maskEx.recycle();
// assign to view
BitmapDrawable res = new BitmapDrawable(getResources(), outerBg);
res.setAntiAlias(true);
return res;
} catch (Exception e) {
}
return null;
}
@Override
protected void onPostExecute(BitmapDrawable res) {
binding.overlayOuter.setImageDrawable(res);
binding.overlayOuter.setVisibility(View.VISIBLE);
binding.overlay.setVisibility(View.INVISIBLE);
}
}
}
package *;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.Camera;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.util.List;
import timber.log.Timber;
import static *.CameraUtils.checkCameraHardware;
import static *.CameraUtils.getCameraInstance;
import static *.CameraUtils.getOptimalFocusModeAuto;
import static *.CameraUtils.getOptimalPictureSize;
import static *.CameraUtils.getOptimalPreviewSize;
public class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
private int CAMERA_ORIENTATION = 90;
private int CAMERA_ORIENTATION_FRONT = 90;
private SurfaceHolder mHolder;
private Camera mCamera;
private int mCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
private Camera.Size size;
public CameraSurfaceView(Context context) {
this(context, null);
}
public CameraSurfaceView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CameraSurfaceView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = getHolder();
mHolder.addCallback(this);
// deprecated setting, but required on Android versions prior to 3.0
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
mHolder.setKeepScreenOn(true);
}
/**
* Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
* calculated from the parameters. Note that the actual sizes of parameters don't matter, that
* is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
*
* @param width Relative horizontal size
* @param height Relative vertical size
*/
public void setAspectRatio(int width, int height) {
if (getParent() instanceof AspectFrameLayout) {
((AspectFrameLayout) getParent()).setAspectRatio((double) height / width);
}
}
public void setCameraFacing(int cameraFacing) {
mCameraId = cameraFacing;
}
public void switchCameraMode() {
setCameraFacing(mCameraId == Camera.CameraInfo.CAMERA_FACING_BACK ?
Camera.CameraInfo.CAMERA_FACING_FRONT :
Camera.CameraInfo.CAMERA_FACING_BACK);
}
public void startPreview() {
if (checkCameraHardware(this.getContext())) {
mCamera = getCameraInstance(mCameraId);
if (mCamera != null) {
initCameraConfig();
// The Surface has been created, now tell the camera where to draw the preview.
try {
mCamera.setPreviewDisplay(mHolder);
mCamera.setDisplayOrientation(mCameraId == Camera.CameraInfo.CAMERA_FACING_BACK ? CAMERA_ORIENTATION : CAMERA_ORIENTATION_FRONT);
mCamera.startPreview();
} catch (Exception e) {
Timber.w(e, "Error setting camera preview");
}
}
}
}
public void stopPreview() {
if (mCamera != null) {
mCamera.stopPreview();
mCamera.release();
}
}
public Camera getCamera() {
return mCamera;
}
void initCameraConfig() {
Camera.Parameters cp = mCamera.getParameters();
cp.setRotation(CAMERA_ORIENTATION);
if (size != null) cp.setPreviewSize(size.width, size.height);
String focusMode = getOptimalFocusModeAuto(cp.getSupportedFocusModes());
if (focusMode != null) cp.setFocusMode(focusMode);
Camera.Size selectedPictureSize = getOptimalPictureSize(
cp.getSupportedPictureSizes(), cp.getPreviewSize());
if (selectedPictureSize != null)
cp.setPictureSize(selectedPictureSize.width, selectedPictureSize.height);
mCamera.setParameters(cp);
}
//region Surface Holder
public void surfaceCreated(SurfaceHolder holder) {
}
public void surfaceDestroyed(SurfaceHolder holder) {
// empty. Take care of releasing the Camera preview in your activity.
}
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
// If your preview can change or rotate, take care of those events here.
// Make sure to stop the preview before resizing or reformatting it.
if (mHolder.getSurface() == null) {
// preview surface does not exist
return;
}
if (mCamera != null) {
size = getOptimalPreviewSize(mCamera.getParameters().getSupportedPreviewSizes(), w, h);
setAspectRatio(size.width, size.height);
startPreview();
}
}
//endregion
public static class CameraUtils {
/**
* Check if this device has a camera
*/
public static boolean checkCameraHardware(Context context) {
// this device has a camera
// no camera on this device
return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
}
/**
* A safe way to get an instance of the Camera object.
*/
public static Camera getCameraInstance() {
Camera c = null;
try {
c = Camera.open(); // attempt to get a Camera instance
} catch (Exception e) {
// Camera is not available (in use or does not exist)
}
return c; // returns null if camera is unavailable
}
/**
* A safe way to get an instance of the Camera object.
*/
public static Camera getCameraInstance(int cameraId) {
Camera c = null;
try {
c = Camera.open(cameraId); // attempt to get a Camera instance
} catch (Exception e) {
// Camera is not available (in use or does not exist)
}
return c; // returns null if camera is unavailable
}
public static Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
if (sizes == null) return null;
Camera.Size optimalSize = null;
double ratio = (double) h / w;
double minDiff = Double.MAX_VALUE;
double newDiff;
for (Camera.Size size : sizes) {
newDiff = Math.abs((double) size.width / size.height - ratio);
if (newDiff < minDiff) {
optimalSize = size;
minDiff = newDiff;
}
}
return optimalSize;
}
public static Camera.Size getOptimalPictureSize(List<Camera.Size> supportedSizes, Camera.Size previewSize) {
// setup picture taken size, same ratio as preview
float aspectRatio = (float) previewSize.height / previewSize.width;
Camera.Size selectedPictureSize = null;
for (Camera.Size s : supportedSizes) {
if (aspectRatio == (float) s.height / s.width) {
if (selectedPictureSize == null)
selectedPictureSize = s;
else if (selectedPictureSize.height <= s.height)
selectedPictureSize = s;
}
}
return selectedPictureSize;
}
public static String getOptimalFocusModeAuto(List<String> supportedMode) {
if (supportedMode.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE))
return Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE;
if (supportedMode.contains(Camera.Parameters.FOCUS_MODE_AUTO))
return Camera.Parameters.FOCUS_MODE_AUTO;
if (supportedMode.contains(Camera.Parameters.FOCUS_MODE_FIXED))
return Camera.Parameters.FOCUS_MODE_FIXED;
if (supportedMode.contains(Camera.Parameters.FOCUS_MODE_INFINITY))
return Camera.Parameters.FOCUS_MODE_INFINITY;
if (supportedMode.contains(Camera.Parameters.FOCUS_MODE_MACRO))
return Camera.Parameters.FOCUS_MODE_MACRO;
return null;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment