Control commit

This commit is contained in:
Captain Arepa 2024-05-31 13:41:34 -04:00
parent fbe834b8e2
commit 0f11ca12bc
13 changed files with 314 additions and 18 deletions

View file

@ -20,6 +20,9 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.CameraXTestAppJava" android:theme="@style/Theme.CameraXTestAppJava"
tools:targetApi="31"> tools:targetApi="31">
<activity
android:name=".SegpassCameraActivity"
android:exported="false" />
<activity <activity
android:name=".CameraActivityNew" android:name=".CameraActivityNew"
android:exported="false" /> android:exported="false" />

View file

@ -76,7 +76,7 @@ public class CameraActivityNew extends AppCompatActivity implements ActivityComp
} }
private void setUpListeners() { private void setUpListeners() {
binding.inCamera2.btnTakepicture.setOnClickListener(v -> mSegpassCamera.takePicture()); binding.inCamera2.btnTakePhoto.setOnClickListener(v -> mSegpassCamera.takePicture());
} }
/** /**

View file

@ -39,7 +39,7 @@ public class MainActivity extends AppCompatActivity {
private void setListeners() { private void setListeners() {
binding.goToCamera.setOnClickListener(v -> { binding.goToCamera.setOnClickListener(v -> {
Intent intent = new Intent(this, CameraActivityNew.class); Intent intent = new Intent(this, SegpassCameraActivity.class);
startActivity(intent); startActivity(intent);
}); });
} }

View file

@ -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();
}
}

View file

@ -46,6 +46,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -57,7 +58,7 @@ public class SegpassCamera {
private static final String TAG = "SegpassCamera"; private static final String TAG = "SegpassCamera";
/** /**
* Class variabels * Class variables
*/ */
AppCompatActivity mActivity; AppCompatActivity mActivity;
SegpassPermissionCallback mPermissionListener; 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) { private boolean isSwappedDimensions(CameraCharacteristics characteristics) {
// Find out if we need to swap dimension to get the preview size relative to sensor // Find out if we need to swap dimension to get the preview size relative to sensor
// coordinate. // coordinate.
@ -429,12 +437,18 @@ public class SegpassCamera {
return swappedDimensions; 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 @Nullable
private static StreamConfigurationMap getStreamConfigurationMap(CameraCharacteristics characteristics) { private static StreamConfigurationMap getStreamConfigurationMap(CameraCharacteristics characteristics) {
// We don't use a back facing camera. // We don't use a back facing camera.
Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING); Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
// If facis is null, return // If facing is null, return
if (facing == null) return null; if (facing == null) return null;
if (facing == CameraMetadata.LENS_FACING_BACK) return null; if (facing == CameraMetadata.LENS_FACING_BACK) return null;
@ -442,6 +456,15 @@ public class SegpassCamera {
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 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) { private void getOptimalPreviewSize(int width, int height, boolean swappedDimensions, StreamConfigurationMap map, Size largest) {
Point displaySize = new Point(); Point displaySize = new Point();
mActivity.getWindowManager().getDefaultDisplay().getSize(displaySize); mActivity.getWindowManager().getDefaultDisplay().getSize(displaySize);
@ -473,6 +496,11 @@ public class SegpassCamera {
maxPreviewHeight, largest); maxPreviewHeight, largest);
} }
/**
* Set image reader listeners
*
* @param largest Largest display (i.e. image) {@link Size} supported by the preview.
*/
private void setImageReaderListeners(Size largest) { private void setImageReaderListeners(Size largest) {
mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),
ImageFormat.JPEG, /*maxImages*/2); ImageFormat.JPEG, /*maxImages*/2);
@ -480,6 +508,9 @@ public class SegpassCamera {
mOnImageAvailableListener, mBackgroundHandler); mOnImageAvailableListener, mBackgroundHandler);
} }
/**
* Sets the preview surface aspect ratio considering the screen orientation/
*/
private void setPreviewAspectRatio() { private void setPreviewAspectRatio() {
// We fit the aspect ratio of TextureView to the size of preview we picked. // We fit the aspect ratio of TextureView to the size of preview we picked.
int orientation = mActivity.getResources().getConfiguration().orientation; int orientation = mActivity.getResources().getConfiguration().orientation;
@ -647,8 +678,8 @@ public class SegpassCamera {
CameraManager manager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE); CameraManager manager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE);
CameraCharacteristics characteristics = manager.getCameraCharacteristics(mCameraId); CameraCharacteristics characteristics = manager.getCameraCharacteristics(mCameraId);
// // Obtains list of image size supported by the selected camera sensor.
Size[] jpegSizes = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputSizes(ImageFormat.JPEG); Size[] jpegSizes = Objects.requireNonNull(characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)).getOutputSizes(ImageFormat.JPEG);
if (jpegSizes == null) throw new NullPointerException("Error taking picture"); if (jpegSizes == null) throw new NullPointerException("Error taking picture");
// Default dimensions // Default dimensions
@ -677,6 +708,7 @@ public class SegpassCamera {
int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation)); captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation));
// Listens when the image is finally taken.
mCaptureImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler); mCaptureImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);
final CameraCaptureSession.CaptureCallback captureListener = new CameraCaptureSession.CaptureCallback() { final CameraCaptureSession.CaptureCallback captureListener = new CameraCaptureSession.CaptureCallback() {
@Override @Override
@ -694,6 +726,7 @@ public class SegpassCamera {
} }
}; };
// Initialized the session to listen to a picture capture event.
mCameraDevice.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() { mCameraDevice.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() {
@Override @Override
public void onConfigured(@NonNull CameraCaptureSession session) { 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 @NonNull
private static ImageReader getCaptureImageReader(int width, int height) { private static ImageReader getCaptureImageReader(int width, int height) {
return ImageReader.newInstance(width, height, ImageFormat.JPEG, 1); 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) { private int getOrientation(int rotation) {
// Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X) // 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. // We have to take that into account and rotate JPEG properly.

View file

@ -1,16 +1,29 @@
package com.example.cameraxtestappjava.segpass.camera.utils; package com.example.cameraxtestappjava.segpass.camera.utils;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.Image; import android.media.Image;
import android.util.Base64; import android.util.Base64;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
public class ImageSaverUtil implements Runnable { public class ImageSaverUtil implements Runnable {
/**
* Tag for the {@link Log}
*/
private static final String TAG = "ImageSaverUtil";
/** /**
* The JPEG image * The JPEG image
*/ */
private final Image mImage; private final Image mImage;
/**
* Callback to return encoded image value in {@link Base64}
*/
private final ImageEncodingCallback mCallback; private final ImageEncodingCallback mCallback;
public ImageSaverUtil(Image image, ImageEncodingCallback callback) { public ImageSaverUtil(Image image, ImageEncodingCallback callback) {
@ -21,12 +34,35 @@ public class ImageSaverUtil implements Runnable {
@Override @Override
public void run() { public void run() {
try { try {
// Get image as byte data buffer
ByteBuffer buffer = mImage.getPlanes()[0].getBuffer(); ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.capacity()]; byte[] bytes = new byte[buffer.capacity()];
buffer.get(bytes); 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) { } catch (Exception e) {
e.printStackTrace(); Log.e(TAG, "Error encoding photo: " + e.getMessage());
mCallback.onImageEncodeFail(e.getMessage()); mCallback.onImageEncodeFail(e.getMessage());
} }
} }

View file

@ -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);
}
}

View file

@ -9,7 +9,7 @@
<include <include
android:id="@+id/inCamera2" android:id="@+id/inCamera2"
layout="@layout/camera_autofit_view" layout="@layout/segpass_camera_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />

View file

@ -9,7 +9,7 @@
<include <include
android:id="@+id/inCamera2" android:id="@+id/inCamera2"
layout="@layout/camera_autofit_view" layout="@layout/segpass_camera_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />

View 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>

View file

@ -11,11 +11,11 @@
android:id="@+id/tvCameraTextureView" android:id="@+id/tvCameraTextureView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_above="@id/btn_takepicture" android:layout_above="@id/btnTakePhoto"
android:layout_alignParentTop="true" /> android:layout_alignParentTop="true" />
<ImageButton <ImageButton
android:id="@+id/btn_takepicture" android:id="@+id/btnTakePhoto"
android:layout_width="80dip" android:layout_width="80dip"
android:layout_height="80dip" android:layout_height="80dip"
android:layout_alignParentBottom="true" android:layout_alignParentBottom="true"
@ -24,6 +24,6 @@
android:contentDescription="take_photo" android:contentDescription="take_photo"
android:padding="10dip" android:padding="10dip"
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:src="@drawable/capture_button_video" /> android:src="@drawable/capture_picture_button" />
</RelativeLayout> </RelativeLayout>

View file

@ -1,11 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="#000000" android:background="#000000"
android:orientation="vertical" android:orientation="vertical">
tools:context=".MainActivity">
<com.example.cameraxtestappjava.segpass.camera.view.AutoFitTextureView <com.example.cameraxtestappjava.segpass.camera.view.AutoFitTextureView
android:id="@+id/tvCameraTextureView" android:id="@+id/tvCameraTextureView"
@ -14,13 +12,13 @@
android:layout_height="wrap_content"/> android:layout_height="wrap_content"/>
<ImageButton <ImageButton
android:id="@+id/btn_takepicture" android:id="@+id/btnTakePhoto"
android:layout_width="80dip" android:layout_width="80dip"
android:layout_height="80dip" android:layout_height="80dip"
android:background="?android:selectableItemBackground" android:background="?android:selectableItemBackground"
android:padding="10dip" android:padding="10dip"
android:layout_gravity="bottom|center" android:layout_gravity="bottom|center"
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:src="@drawable/capture_button_video" /> android:src="@drawable/capture_picture_button" />
</FrameLayout> </FrameLayout>