Last active
May 19, 2022 07:00
-
-
Save esabook/a712d8955483f5b0f685eb3ab15abbc1 to your computer and use it in GitHub Desktop.
[Android] CameraActivity: FrontCam/MainCam, GreenScreen base for MaskingIndicator/Crop
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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