diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 3915541..0550ef1 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -20,6 +20,9 @@
android:supportsRtl="true"
android:theme="@style/Theme.CameraXTestAppJava"
tools:targetApi="31">
+
diff --git a/app/src/main/java/com/example/cameraxtestappjava/CameraActivityNew.java b/app/src/main/java/com/example/cameraxtestappjava/CameraActivityNew.java
index 74e2e87..76ea9e5 100644
--- a/app/src/main/java/com/example/cameraxtestappjava/CameraActivityNew.java
+++ b/app/src/main/java/com/example/cameraxtestappjava/CameraActivityNew.java
@@ -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());
}
/**
diff --git a/app/src/main/java/com/example/cameraxtestappjava/MainActivity.java b/app/src/main/java/com/example/cameraxtestappjava/MainActivity.java
index 1c4799c..f4bfa88 100644
--- a/app/src/main/java/com/example/cameraxtestappjava/MainActivity.java
+++ b/app/src/main/java/com/example/cameraxtestappjava/MainActivity.java
@@ -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);
});
}
diff --git a/app/src/main/java/com/example/cameraxtestappjava/SegpassCameraActivity.java b/app/src/main/java/com/example/cameraxtestappjava/SegpassCameraActivity.java
new file mode 100644
index 0000000..5ca8e9f
--- /dev/null
+++ b/app/src/main/java/com/example/cameraxtestappjava/SegpassCameraActivity.java
@@ -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();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/cameraxtestappjava/segpass/SegpassCamera.java b/app/src/main/java/com/example/cameraxtestappjava/segpass/SegpassCamera.java
index ff3e450..93f1dff 100644
--- a/app/src/main/java/com/example/cameraxtestappjava/segpass/SegpassCamera.java
+++ b/app/src/main/java/com/example/cameraxtestappjava/segpass/SegpassCamera.java
@@ -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.
diff --git a/app/src/main/java/com/example/cameraxtestappjava/segpass/camera/utils/ImageSaverUtil.java b/app/src/main/java/com/example/cameraxtestappjava/segpass/camera/utils/ImageSaverUtil.java
index 2713aa3..747e5cc 100644
--- a/app/src/main/java/com/example/cameraxtestappjava/segpass/camera/utils/ImageSaverUtil.java
+++ b/app/src/main/java/com/example/cameraxtestappjava/segpass/camera/utils/ImageSaverUtil.java
@@ -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());
}
}
diff --git a/app/src/main/java/com/example/cameraxtestappjava/segpass/camera/view/SegpassCameraLayout.java b/app/src/main/java/com/example/cameraxtestappjava/segpass/camera/view/SegpassCameraLayout.java
new file mode 100644
index 0000000..8018f30
--- /dev/null
+++ b/app/src/main/java/com/example/cameraxtestappjava/segpass/camera/view/SegpassCameraLayout.java
@@ -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);
+ }
+}
diff --git a/app/src/main/res/drawable/capture_button_video.xml b/app/src/main/res/drawable/capture_picture_button.xml
similarity index 100%
rename from app/src/main/res/drawable/capture_button_video.xml
rename to app/src/main/res/drawable/capture_picture_button.xml
diff --git a/app/src/main/res/layout/activity_camera.xml b/app/src/main/res/layout/activity_camera.xml
index 07fcc4b..d85225c 100644
--- a/app/src/main/res/layout/activity_camera.xml
+++ b/app/src/main/res/layout/activity_camera.xml
@@ -9,7 +9,7 @@
diff --git a/app/src/main/res/layout/activity_camera_new.xml b/app/src/main/res/layout/activity_camera_new.xml
index a304607..050b3df 100644
--- a/app/src/main/res/layout/activity_camera_new.xml
+++ b/app/src/main/res/layout/activity_camera_new.xml
@@ -9,7 +9,7 @@
diff --git a/app/src/main/res/layout/activity_segpass_camera.xml b/app/src/main/res/layout/activity_segpass_camera.xml
new file mode 100644
index 0000000..f40f564
--- /dev/null
+++ b/app/src/main/res/layout/activity_segpass_camera.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/custom_camera2_view.xml b/app/src/main/res/layout/custom_camera2_view.xml
index 5f189cd..c28d3cf 100644
--- a/app/src/main/res/layout/custom_camera2_view.xml
+++ b/app/src/main/res/layout/custom_camera2_view.xml
@@ -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" />
+ android:src="@drawable/capture_picture_button" />
diff --git a/app/src/main/res/layout/camera_autofit_view.xml b/app/src/main/res/layout/segpass_camera_view.xml
similarity index 77%
rename from app/src/main/res/layout/camera_autofit_view.xml
rename to app/src/main/res/layout/segpass_camera_view.xml
index a3d0584..f8387b7 100644
--- a/app/src/main/res/layout/camera_autofit_view.xml
+++ b/app/src/main/res/layout/segpass_camera_view.xml
@@ -1,11 +1,9 @@
+ android:orientation="vertical">
+ android:src="@drawable/capture_picture_button" />