Control commit
This commit is contained in:
parent
fbe834b8e2
commit
0f11ca12bc
13 changed files with 314 additions and 18 deletions
|
@ -20,6 +20,9 @@
|
|||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.CameraXTestAppJava"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".SegpassCameraActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".CameraActivityNew"
|
||||
android:exported="false" />
|
||||
|
|
|
@ -76,7 +76,7 @@ public class CameraActivityNew extends AppCompatActivity implements ActivityComp
|
|||
}
|
||||
|
||||
private void setUpListeners() {
|
||||
binding.inCamera2.btnTakepicture.setOnClickListener(v -> mSegpassCamera.takePicture());
|
||||
binding.inCamera2.btnTakePhoto.setOnClickListener(v -> mSegpassCamera.takePicture());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -39,7 +39,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
|
||||
private void setListeners() {
|
||||
binding.goToCamera.setOnClickListener(v -> {
|
||||
Intent intent = new Intent(this, CameraActivityNew.class);
|
||||
Intent intent = new Intent(this, SegpassCameraActivity.class);
|
||||
startActivity(intent);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
package com.example.cameraxtestappjava;
|
||||
|
||||
import static com.example.cameraxtestappjava.segpass.SegpassCamera.REQUEST_CAMERA_PERMISSION;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.activity.EdgeToEdge;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.graphics.Insets;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
|
||||
import com.example.cameraxtestappjava.databinding.ActivitySegpassCameraBinding;
|
||||
import com.example.cameraxtestappjava.segpass.camera.utils.SegpassCameraCallback;
|
||||
import com.example.cameraxtestappjava.segpass.camera.utils.SegpassPermissionCallback;
|
||||
import com.example.cameraxtestappjava.segpass.camera.view.SegpassCameraLayout;
|
||||
|
||||
public class SegpassCameraActivity extends AppCompatActivity implements ActivityCompat.OnRequestPermissionsResultCallback, SegpassCameraCallback, SegpassPermissionCallback {
|
||||
|
||||
ActivitySegpassCameraBinding binding;
|
||||
SegpassCameraLayout cameraLayout;
|
||||
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
EdgeToEdge.enable(this);
|
||||
|
||||
binding = ActivitySegpassCameraBinding.inflate(getLayoutInflater());
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
||||
setContentView(binding.getRoot());
|
||||
|
||||
setCamera();
|
||||
|
||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
|
||||
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
|
||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
|
||||
return insets;
|
||||
});
|
||||
}
|
||||
|
||||
private void setCamera() {
|
||||
cameraLayout = binding.sclCameraLayout;
|
||||
cameraLayout.initSegpassCamera(this, this, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
cameraLayout.resumeCamera();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
cameraLayout.pauseCamera();
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
|
||||
@NonNull int[] grantResults) {
|
||||
if (requestCode == REQUEST_CAMERA_PERMISSION) {
|
||||
if (grantResults.length != 2 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
|
||||
onPermissionDenied();
|
||||
}
|
||||
} else {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Camera callback
|
||||
*/
|
||||
|
||||
@Override
|
||||
public void onPictureTakenSuccess(String base64) {
|
||||
cameraLayout.showToast("Base64 generated.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPictureTakenFailError(String error) {
|
||||
cameraLayout.showToast(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Camera state callback (just to validate if there was an error while initializing the camera)
|
||||
*/
|
||||
|
||||
@Override
|
||||
public void onCameraInitError(String errorMessage) {
|
||||
cameraLayout.showToast(errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Permissions callbck
|
||||
*/
|
||||
|
||||
@Override
|
||||
public void onPermissionRequest() {
|
||||
requestPermissions(new String[]{android.Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CAMERA_PERMISSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPermissionGranted() {
|
||||
// Do nothing, use camera.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPermissionDenied() {
|
||||
cameraLayout.showToast("No permissions granted, closing camera.");
|
||||
finish();
|
||||
}
|
||||
}
|
|
@ -46,6 +46,7 @@ import java.util.ArrayList;
|
|||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
@ -57,7 +58,7 @@ public class SegpassCamera {
|
|||
private static final String TAG = "SegpassCamera";
|
||||
|
||||
/**
|
||||
* Class variabels
|
||||
* Class variables
|
||||
*/
|
||||
AppCompatActivity mActivity;
|
||||
SegpassPermissionCallback mPermissionListener;
|
||||
|
@ -403,6 +404,13 @@ public class SegpassCamera {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates whether the camera's current rotation is different from the output shown in the
|
||||
* preview {@link AutoFitTextureView}
|
||||
*
|
||||
* @param characteristics Camera characteristics
|
||||
* @return boolean value for display rotation
|
||||
*/
|
||||
private boolean isSwappedDimensions(CameraCharacteristics characteristics) {
|
||||
// Find out if we need to swap dimension to get the preview size relative to sensor
|
||||
// coordinate.
|
||||
|
@ -429,12 +437,18 @@ public class SegpassCamera {
|
|||
return swappedDimensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains configuration map to be applied to the preview surface
|
||||
*
|
||||
* @param characteristics Camera characteristics
|
||||
* @return the configuration map for the {@link AutoFitTextureView}
|
||||
*/
|
||||
@Nullable
|
||||
private static StreamConfigurationMap getStreamConfigurationMap(CameraCharacteristics characteristics) {
|
||||
// We don't use a back facing camera.
|
||||
Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
|
||||
|
||||
// If facis is null, return
|
||||
// If facing is null, return
|
||||
if (facing == null) return null;
|
||||
if (facing == CameraMetadata.LENS_FACING_BACK) return null;
|
||||
|
||||
|
@ -442,6 +456,15 @@ public class SegpassCamera {
|
|||
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains optimal preview size to be shown in {@link AutoFitTextureView}
|
||||
*
|
||||
* @param width Current {@link AutoFitTextureView} width
|
||||
* @param height Current {@link AutoFitTextureView} height
|
||||
* @param swappedDimensions Whether the preview surface is rotate in relation to the previwe
|
||||
* @param map Configuration map to be applies to the preview surface
|
||||
* @param largest Largest display (i.e. image) {@link Size} supported by the preview.
|
||||
*/
|
||||
private void getOptimalPreviewSize(int width, int height, boolean swappedDimensions, StreamConfigurationMap map, Size largest) {
|
||||
Point displaySize = new Point();
|
||||
mActivity.getWindowManager().getDefaultDisplay().getSize(displaySize);
|
||||
|
@ -473,6 +496,11 @@ public class SegpassCamera {
|
|||
maxPreviewHeight, largest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set image reader listeners
|
||||
*
|
||||
* @param largest Largest display (i.e. image) {@link Size} supported by the preview.
|
||||
*/
|
||||
private void setImageReaderListeners(Size largest) {
|
||||
mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),
|
||||
ImageFormat.JPEG, /*maxImages*/2);
|
||||
|
@ -480,6 +508,9 @@ public class SegpassCamera {
|
|||
mOnImageAvailableListener, mBackgroundHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the preview surface aspect ratio considering the screen orientation/
|
||||
*/
|
||||
private void setPreviewAspectRatio() {
|
||||
// We fit the aspect ratio of TextureView to the size of preview we picked.
|
||||
int orientation = mActivity.getResources().getConfiguration().orientation;
|
||||
|
@ -647,8 +678,8 @@ public class SegpassCamera {
|
|||
CameraManager manager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE);
|
||||
CameraCharacteristics characteristics = manager.getCameraCharacteristics(mCameraId);
|
||||
|
||||
//
|
||||
Size[] jpegSizes = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputSizes(ImageFormat.JPEG);
|
||||
// Obtains list of image size supported by the selected camera sensor.
|
||||
Size[] jpegSizes = Objects.requireNonNull(characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)).getOutputSizes(ImageFormat.JPEG);
|
||||
if (jpegSizes == null) throw new NullPointerException("Error taking picture");
|
||||
|
||||
// Default dimensions
|
||||
|
@ -677,6 +708,7 @@ public class SegpassCamera {
|
|||
int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
|
||||
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation));
|
||||
|
||||
// Listens when the image is finally taken.
|
||||
mCaptureImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);
|
||||
final CameraCaptureSession.CaptureCallback captureListener = new CameraCaptureSession.CaptureCallback() {
|
||||
@Override
|
||||
|
@ -694,6 +726,7 @@ public class SegpassCamera {
|
|||
}
|
||||
};
|
||||
|
||||
// Initialized the session to listen to a picture capture event.
|
||||
mCameraDevice.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() {
|
||||
@Override
|
||||
public void onConfigured(@NonNull CameraCaptureSession session) {
|
||||
|
@ -717,11 +750,24 @@ public class SegpassCamera {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ImageReader for capturing a still picture.
|
||||
*
|
||||
* @param width Supported image width by surface and sensor.
|
||||
* @param height Supported image height by surface and sensor.
|
||||
* @return {@link ImageReader} object for the preview surface.
|
||||
*/
|
||||
@NonNull
|
||||
private static ImageReader getCaptureImageReader(int width, int height) {
|
||||
return ImageReader.newInstance(width, height, ImageFormat.JPEG, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains orientation that will be applied to the captured image.
|
||||
*
|
||||
* @param rotation Rotation angle.
|
||||
* @return Orientation value.
|
||||
*/
|
||||
private int getOrientation(int rotation) {
|
||||
// Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X)
|
||||
// We have to take that into account and rotate JPEG properly.
|
||||
|
|
|
@ -1,16 +1,29 @@
|
|||
package com.example.cameraxtestappjava.segpass.camera.utils;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.media.Image;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ImageSaverUtil implements Runnable {
|
||||
|
||||
/**
|
||||
* Tag for the {@link Log}
|
||||
*/
|
||||
private static final String TAG = "ImageSaverUtil";
|
||||
|
||||
/**
|
||||
* The JPEG image
|
||||
*/
|
||||
private final Image mImage;
|
||||
|
||||
/**
|
||||
* Callback to return encoded image value in {@link Base64}
|
||||
*/
|
||||
private final ImageEncodingCallback mCallback;
|
||||
|
||||
public ImageSaverUtil(Image image, ImageEncodingCallback callback) {
|
||||
|
@ -21,12 +34,35 @@ public class ImageSaverUtil implements Runnable {
|
|||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
|
||||
// Get image as byte data buffer
|
||||
ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
|
||||
byte[] bytes = new byte[buffer.capacity()];
|
||||
buffer.get(bytes);
|
||||
mCallback.onImageEncodeSuccess(Base64.encodeToString(bytes, Base64.NO_WRAP));
|
||||
|
||||
// Convert byte data to a Bitmap
|
||||
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
|
||||
|
||||
// Create a ByteArrayOutputStream to capture the compressed image data
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
// Compress the bitmap with JPEG format and desired quality (25% will do for this use case)
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 25, outputStream);
|
||||
|
||||
// Get the compressed image data as a byte array
|
||||
byte[] compressedImageData = outputStream.toByteArray();
|
||||
|
||||
// Encode the compressed data to Base64 string
|
||||
String base64String = Base64.encodeToString(compressedImageData, Base64.NO_WRAP);
|
||||
|
||||
// Close the stream (optional, as ByteArrayOutputStream should close automatically)
|
||||
outputStream.close();
|
||||
|
||||
// Return Base64 string on callback
|
||||
mCallback.onImageEncodeSuccess(base64String);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "Error encoding photo: " + e.getMessage());
|
||||
mCallback.onImageEncodeFail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
package com.example.cameraxtestappjava.segpass.camera.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.example.cameraxtestappjava.R;
|
||||
import com.example.cameraxtestappjava.segpass.SegpassCamera;
|
||||
import com.example.cameraxtestappjava.segpass.camera.utils.SegpassCameraCallback;
|
||||
import com.example.cameraxtestappjava.segpass.camera.utils.SegpassPermissionCallback;
|
||||
|
||||
public class SegpassCameraLayout extends FrameLayout {
|
||||
private AppCompatActivity mActivity;
|
||||
private AutoFitTextureView mTextureView;
|
||||
private SegpassPermissionCallback mPermissionListener;
|
||||
private SegpassCameraCallback mCameraCallback;
|
||||
private SegpassCamera mCamera;
|
||||
private ImageButton mTakePicture;
|
||||
|
||||
private final View rootView;
|
||||
|
||||
public SegpassCameraLayout(@NonNull Context context) {
|
||||
super(context);
|
||||
rootView = inflate(context, R.layout.segpass_camera_view, this);
|
||||
}
|
||||
|
||||
public SegpassCameraLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
rootView = inflate(context, R.layout.segpass_camera_view, this);
|
||||
}
|
||||
|
||||
public SegpassCameraLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
rootView = inflate(context, R.layout.segpass_camera_view, this);
|
||||
}
|
||||
|
||||
public SegpassCameraLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
rootView = inflate(context, R.layout.segpass_camera_view, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param activity Parent activity where the camera is called from.
|
||||
* @param listener {@link SegpassPermissionCallback} that deals with camera and storage permissions.
|
||||
* @param cameraCallback {@link SegpassCameraCallback} that deals with operation results.
|
||||
*/
|
||||
public void initSegpassCamera(AppCompatActivity activity, SegpassPermissionCallback listener, SegpassCameraCallback cameraCallback) {
|
||||
mActivity = activity;
|
||||
mPermissionListener = listener;
|
||||
mCameraCallback = cameraCallback;
|
||||
mTextureView = rootView.findViewById(R.id.tvCameraTextureView);
|
||||
mCamera = new SegpassCamera(mActivity, mTextureView, mPermissionListener, mCameraCallback);
|
||||
mCamera.resumeCamera();
|
||||
setUpListeners();
|
||||
}
|
||||
|
||||
private void setUpListeners() {
|
||||
mTakePicture = rootView.findViewById(R.id.btnTakePhoto);
|
||||
mTakePicture.setOnClickListener(v -> mCamera.takePicture());
|
||||
}
|
||||
|
||||
public void resumeCamera() {
|
||||
mCamera.resumeCamera();
|
||||
}
|
||||
|
||||
public void pauseCamera() {
|
||||
mCamera.pauseCamera();
|
||||
}
|
||||
|
||||
public void showToast(String message) {
|
||||
mCamera.showToast(message);
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
<include
|
||||
android:id="@+id/inCamera2"
|
||||
layout="@layout/camera_autofit_view"
|
||||
layout="@layout/segpass_camera_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
<include
|
||||
android:id="@+id/inCamera2"
|
||||
layout="@layout/camera_autofit_view"
|
||||
layout="@layout/segpass_camera_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
|
|
14
app/src/main/res/layout/activity_segpass_camera.xml
Normal file
14
app/src/main/res/layout/activity_segpass_camera.xml
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".SegpassCameraActivity">
|
||||
|
||||
<com.example.cameraxtestappjava.segpass.camera.view.SegpassCameraLayout
|
||||
android:id="@+id/sclCameraLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -11,11 +11,11 @@
|
|||
android:id="@+id/tvCameraTextureView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_above="@id/btn_takepicture"
|
||||
android:layout_above="@id/btnTakePhoto"
|
||||
android:layout_alignParentTop="true" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_takepicture"
|
||||
android:id="@+id/btnTakePhoto"
|
||||
android:layout_width="80dip"
|
||||
android:layout_height="80dip"
|
||||
android:layout_alignParentBottom="true"
|
||||
|
@ -24,6 +24,6 @@
|
|||
android:contentDescription="take_photo"
|
||||
android:padding="10dip"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/capture_button_video" />
|
||||
android:src="@drawable/capture_picture_button" />
|
||||
</RelativeLayout>
|
||||
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#000000"
|
||||
android:orientation="vertical"
|
||||
tools:context=".MainActivity">
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.example.cameraxtestappjava.segpass.camera.view.AutoFitTextureView
|
||||
android:id="@+id/tvCameraTextureView"
|
||||
|
@ -14,13 +12,13 @@
|
|||
android:layout_height="wrap_content"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_takepicture"
|
||||
android:id="@+id/btnTakePhoto"
|
||||
android:layout_width="80dip"
|
||||
android:layout_height="80dip"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:padding="10dip"
|
||||
android:layout_gravity="bottom|center"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/capture_button_video" />
|
||||
android:src="@drawable/capture_picture_button" />
|
||||
</FrameLayout>
|
||||
|
Loading…
Reference in a new issue